1
0
Эх сурвалжийг харах

Improve display of renew dates

Fixed node renew notify when no node required renew;
BrettonYe 9 сар өмнө
parent
commit
2ce78a9b5d

+ 3 - 1
app/Console/Commands/NodeDailyMaintenance.php

@@ -82,7 +82,9 @@ class NodeDailyMaintenance extends Command
 
         $nodes = Node::whereNotNull('details')->whereIn('details->next_renewal_date', $notificationDates)->pluck('name', 'id')->toArray();
 
-        Notification::send(User::find(1), new NodeRenewal($nodes));
+        if (! empty($nodes)) {
+            Notification::send(User::find(1), new NodeRenewal($nodes));
+        }
     }
 
     private function updateNodeRenewal(): void

+ 1 - 1
app/Http/Controllers/Admin/NodeController.php

@@ -133,7 +133,7 @@ class NodeController extends Controller
             'rule_group_id' => $info['rule_group_id'],
             'speed_limit' => $info['speed_limit'],
             'client_limit' => $info['client_limit'],
-            'details' => $details,
+            'details' => ! empty($details) ? $details : null,
             'description' => $info['description'],
             'profile' => $profile ?? [],
             'traffic_rate' => $info['traffic_rate'],

+ 151 - 109
resources/views/admin/node/info.blade.php

@@ -7,6 +7,10 @@
         .hidden {
             display: none
         }
+
+        .bootstrap-select .dropdown-menu {
+            max-height: 50vh !important;
+        }
     </style>
 @endsection
 @section('content')
@@ -132,8 +136,11 @@
                                     <!-- 节点 细则部分 -->
                                     <div class="form-group row">
                                         <label class="col-md-3 col-form-label" for="next_renewal_date">{{ trans('model.node.next_renewal_date') }}</label>
-                                        <input class="form-control col-md-4" id="next_renewal_date" name="next_renewal_date" data-plugin="datepicker"
-                                               type="text" autocomplete="off" />
+                                        <div class="col-md-6 input-group p-0">
+                                            <input class="form-control" id="next_renewal_date" name="next_renewal_date" data-plugin="datepicker" type="text"
+                                                   autocomplete="off" />
+                                            <input class="form-control" id="next_next_renewal_date" type="text" readonly />
+                                        </div>
                                     </div>
                                     <div class="form-group row">
                                         <label class="col-md-3 col-form-label" for="subscription_term_value">
@@ -489,110 +496,156 @@
     <script src="/assets/global/vendor/switchery/switchery.min.js"></script>
     <script src="/assets/global/js/Plugin/switchery.js"></script>
     <script>
+        const string = "{{ strtolower(Str::random()) }}";
+
         $('[name="next_renewal_date"]').datepicker({
-            format: 'yyyy-mm-dd',
+            format: 'yyyy-mm-dd'
         });
 
-        const string = "{{ strtolower(Str::random()) }}";
+        function calculateNextNextRenewalDate() {
+            const nextRenewalDate = $('#next_renewal_date').val();
+            const termValue = parseInt($('#subscription_term_value').val() || 0);
+            const termUnit = $('#subscription_term_unit').val();
+
+            if (!nextRenewalDate || termValue <= 0) {
+                $('#next_next_renewal_date').val('');
+                return;
+            }
+
+            const currentDate = new Date(nextRenewalDate);
+            const originalDay = currentDate.getDate();
+
+            if (termUnit === 'months') {
+                // 获取当前月份和年份
+                let targetMonth = currentDate.getMonth() + termValue;
+                let targetYear = currentDate.getFullYear() + Math.floor(targetMonth / 12);
+                targetMonth = targetMonth % 12;
+
+                // 先将日期设置为目标月的同一天
+                currentDate.setFullYear(targetYear, targetMonth, originalDay);
+
+                // 检查是否因月份天数不同而被自动调整
+                if (currentDate.getMonth() !== targetMonth) {
+                    // 如果被调整,说明目标月份的天数比原始日期少
+                    // 将日期设置为目标月份的最后一天
+                    currentDate.setFullYear(targetYear, targetMonth + 1, 0);
+                }
+            } else {
+                // 处理天数和年份的情况
+                const adjustments = {
+                    days: 'Date',
+                    years: 'FullYear'
+                };
+                currentDate[`set${adjustments[termUnit]}`](
+                    currentDate[`get${adjustments[termUnit]}`]() + termValue
+                );
+            }
+
+            $('#next_next_renewal_date').val(currentDate.toISOString().split('T')[0]);
+        }
 
         $(document).ready(function() {
+            initializeUI(); // 初始化
+            bindEvents(); // 事件绑定
+
+            @isset($node)
+                setupNodeData(@json($node));
+            @else
+                setupDefaultValues();
+            @endisset
+
+        });
+
+        function initializeUI() {
             $('.single-setting').hide();
+            $('#v2_path').val('/' + string);
+        }
+
+        function bindEvents() {
             $('input:radio[name="type"]').on('change', updateServiceType);
             $('#obfs').on('changed.bs.select', toggleObfsParam);
             $('#relay_node_id').on('changed.bs.select', toggleRelayConfig);
             $('#v2_net').on('changed.bs.select', updateV2RaySettings);
+            $('#nodeForm').on('submit', formSubmit);
+            $(document).on('change', '#next_renewal_date, #subscription_term_value, #subscription_term_unit', calculateNextNextRenewalDate);
+        }
 
-            $('#nodeForm').on('submit', function(event) {
-                event.preventDefault();
-                formSubmit(event);
-            });
+        function setupNodeData(nodeData) {
+            const {
+                type,
+                labels,
+                relay_node_id,
+                port,
+                profile,
+                tls_provider,
+                details
+            } = nodeData;
 
-            $('[name="next_renewal_date"]').datepicker({
-                format: 'yyyy-mm-dd'
-            });
+            // 设置选项和输入值
+            ['is_ddns', 'is_udp', 'status'].forEach(prop => nodeData[prop] && $(`#${prop}`).click());
+            ['is_display', 'detection_type', 'type'].forEach(prop => $(`input[name="${prop}"][value="${nodeData[prop]}"]`).click());
+            ['name', 'server', 'ip', 'ipv6', 'push_port', 'traffic_rate', 'speed_limit', 'client_limit', 'description', 'sort']
+            .forEach(prop => $(`#${prop}`).val(nodeData[prop]));
+            ['level', 'rule_group_id', 'country_code', 'relay_node_id'].forEach(prop => $(`#${prop}`).selectpicker('val', nodeData[prop]));
+            $('#labels').selectpicker('val', labels.map(label => label.id));
 
-            toggleObfsParam();
-            toggleRelayConfig();
+            if (details?.next_renewal_date) $('#next_renewal_date').datepicker('update', details.next_renewal_date);
+            if (details?.subscription_term) setSubscriptionTerm(details.subscription_term);
+            if (details?.renewal_cost) $('#renewal_cost').val(details.renewal_cost);
+            calculateNextNextRenewalDate(); // 手动触发计算下下次续费日期
 
-            @isset($node)
-                const nodeData = @json($node);
-                const {
-                    type,
-                    labels,
-                    relay_node_id,
-                    port,
-                    profile,
-                    tls_provider,
-                    details
-                } = nodeData;
-
-                ['is_ddns', 'is_udp', 'status'].forEach(prop => nodeData[prop] && $(`#${prop}`).click());
-                ['is_display', 'detection_type', 'type'].forEach(prop => $(`input[name="${prop}"][value="${nodeData[prop]}"]`).click());
-
-                ['name', 'server', 'ip', 'ipv6', 'push_port', 'traffic_rate', 'speed_limit', 'client_limit', 'description', 'sort']
-                .forEach(prop => $(`#${prop}`).val(nodeData[prop]));
-
-                ['level', 'rule_group_id', 'country_code', 'relay_node_id'].forEach(prop => $(`#${prop}`).selectpicker('val', nodeData[prop]));
-
-                $('#labels').selectpicker('val', labels.map(label => label.id));
-                if (details?.next_renewal_date) {
-                    $('#next_renewal_date').datepicker('update', details.next_renewal_date);
-                }
-                if (details?.subscription_term) {
-                    setSubscriptionTerm(details.subscription_term)
-                }
-                if (details?.renewal_cost) {
-                    $('#renewal_cost').val(details.renewal_cost);
-                }
+            if (relay_node_id) {
+                $('#relay_port').val(port);
+            } else {
+                setupNodeTypeHandlers(type, profile, port, tls_provider);
+            }
 
-                if (relay_node_id) {
-                    $('#relay_port').val(port);
-                } else {
-                    const typeHandlers = {
-                        0: () => $('#method').selectpicker('val', profile?.method || null),
-                        1: setSSRValues,
-                        2: setV2RayValues,
-                        3: () => $('#trojan_port').val(port),
-                        4: setSSRValues
-                    };
-
-                    typeHandlers[type] && typeHandlers[type]();
-                    $('input[name="port"]').val(port);
-                }
+            function setSubscriptionTerm(term) {
+                const [value, unit] = term.split(' ');
 
-                function setSSRValues() {
-                    ['protocol', 'obfs'].forEach(prop => $(`#${prop}`).selectpicker('val', profile[prop] || null));
-                    ['protocol_param', 'obfs_param'].forEach(prop => $(`#${prop}`).val(profile[prop] || null));
-                    if (profile.passwd && port) {
-                        $('#single').click();
-                        $('#passwd').val(profile.passwd);
-                    }
-                }
+                $('#subscription_term_value').val(value || '');
+                $('#subscription_term_unit').selectpicker('val', unit || 'day'); // 默认选择 day
+            }
+        }
+
+        function setupDefaultValues() {
+            switchSetting('single');
+            switchSetting('is_ddns');
+            $('input[name="type"][value="0"]').click();
+            $('#status, #is_udp').click();
+        }
+
+        function setupNodeTypeHandlers(type, profile, port, tls_provider) {
+            const typeHandlers = {
+                0: () => $('#method').selectpicker('val', profile?.method || null),
+                1: setSSRValues,
+                2: setV2RayValues,
+                3: () => $('#trojan_port').val(port),
+                4: setSSRValues
+            };
 
-                function setV2RayValues() {
-                    ['v2_alter_id', 'v2_host', 'v2_sni', 'v2_path'].forEach(prop => $(`#${prop}`).val(profile[prop] || null));
-                    ['v2_net', 'v2_type'].forEach(prop => $(`#${prop}`).selectpicker('val', profile[prop] || null));
-                    $('#v2_method').selectpicker('val', profile['method'] || null);
+            typeHandlers[type] && typeHandlers[type]();
+            $('input[name="port"]').val(port);
 
-                    $('#v2_port').val(port);
-                    profile.v2_tls && $('#v2_tls').click();
-                    $('#tls_provider').val(tls_provider);
+            function setSSRValues() {
+                ['protocol', 'obfs'].forEach(prop => $(`#${prop}`).selectpicker('val', profile[prop] || null));
+                ['protocol_param', 'obfs_param'].forEach(prop => $(`#${prop}`).val(profile[prop] || null));
+                if (profile.passwd && port) {
+                    $('#single').click();
+                    $('#passwd').val(profile.passwd);
                 }
-            @else
-                switchSetting('single');
-                switchSetting('is_ddns');
-                $('input[name="type"][value="0"]').click();
-                $('#status, #is_udp').click();
-                $('#v2_path').val('/' + string);
-            @endisset
+            }
 
-            function setSubscriptionTerm(term) {
-                const [value, unit] = term.split(' ');
+            function setV2RayValues() {
+                ['v2_alter_id', 'v2_host', 'v2_sni', 'v2_path'].forEach(prop => $(`#${prop}`).val(profile[prop] || null));
+                ['v2_net', 'v2_type'].forEach(prop => $(`#${prop}`).selectpicker('val', profile[prop] || null));
+                $('#v2_method').selectpicker('val', profile['method'] || null);
 
-                $('#subscription_term_value').val(value || '');
-                $('#subscription_term_unit').selectpicker('val', unit || 'day'); // 默认选择 day
+                $('#v2_port').val(port);
+                profile.v2_tls && $('#v2_tls').click();
+                $('#tls_provider').val(tls_provider);
             }
-        });
+        }
 
         function formSubmit(event) {
             event.preventDefault(); // 阻止表单的默认提交行为
@@ -675,34 +728,23 @@
         // 设置服务类型
         function updateServiceType() {
             const type = parseInt($(this).val());
+            const settingsMap = {
+                0: ['.ss-setting'],
+                1: ['.ss-setting', '.ssr-setting'],
+                2: ['.v2ray-setting', '#v2_port'],
+                3: ['.trojan-setting', '#trojan_port'],
+                4: ['.ss-setting', '.ssr-setting'],
+            };
+
             $('.ss-setting, .ssr-setting, .v2ray-setting, .trojan-setting').hide();
-            $('#v2_port').removeAttr('required').attr('hidden', true);
-            $('#trojan_port').removeAttr('required');
-            switch (type) {
-                case 0:
-                    $('.ss-setting').show();
-                    break;
-                case 2:
-                    $('.v2ray-setting').show();
-                    $('#v2_port').removeAttr('hidden').prop('required', true);
-                    $('#v2_net').selectpicker('val', 'tcp');
-                    break;
-                case 3:
-                    $('.trojan-setting').show();
-                    $('#trojan_port').removeAttr('hidden').prop('required', true);
-                    break;
-                case 1:
-                case 4:
-                    $('.ss-setting, .ssr-setting').show();
-                    break;
-            }
+            Object.keys(settingsMap).forEach(key => $(settingsMap[key].join(',')).hide());
+            (settingsMap[type] || []).forEach(selector => $(selector).show());
         }
 
         function toggleObfsParam() {
             const $obfsParam = $('.obfs_param');
-            const isPlain = $('#obfs').val() === 'plain';
-            $obfsParam.toggle(!isPlain);
-            if (isPlain) $('#obfs_param').val('');
+            $obfsParam.toggle($('#obfs').val() !== 'plain');
+            if ($('#obfs').val() === 'plain') $('#obfs_param').val('');
         }
 
         function toggleRelayConfig() {
@@ -710,8 +752,8 @@
             $('.relay-config').toggle(hasRelay);
             $('.proxy-config').toggle(!hasRelay);
             $('#relay_port').attr({
-                'hidden': !hasRelay,
-                'required': hasRelay
+                hidden: !hasRelay,
+                required: hasRelay
             });
         }