Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions .github/workflows/lint-snmp-monitoring.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Lint SNMP Monitoring

on:
push:
branches: [main, master]
paths:
- "backend/control_center/snmp_monitoring/**"
- ".github/workflows/lint-snmp-monitoring.yml"
pull_request:
branches: [main, master]
paths:
- "backend/control_center/snmp_monitoring/**"
- ".github/workflows/lint-snmp-monitoring.yml"

jobs:
lint:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.10"

- name: Install flake8
run: pip install flake8

- name: Run flake8 on snmp_monitoring
run: flake8 backend/control_center/snmp_monitoring/ --max-line-length=120
3 changes: 2 additions & 1 deletion backend/control_center/control_center/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
'channels',
# custom apps
'network_device',
'snmp_monitoring',
'odl',
'software_plugin',
'classifier',
Expand Down Expand Up @@ -224,7 +225,7 @@
APP_LOGGERS = [
'controller', 'general', 'ovs_install', 'ovs_management',
'software_plugin', 'utils', 'network_device', 'odl', 'onos',
'account', 'notification', 'device_monitoring', 'network_data', 'classifier'
'account', 'notification', 'device_monitoring', 'network_data', 'classifier', 'snmp_monitoring'
]

LOGGING = {
Expand Down
3 changes: 2 additions & 1 deletion backend/control_center/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,5 @@ service-identity==24.1.0
pyOpenSSL==24.1.0
python-dotenv==1.0.0
uvicorn[standard]
pipreqs==0.4.12
pipreqs==0.4.12
pysnmp==7.1.22
Empty file.
3 changes: 3 additions & 0 deletions backend/control_center/snmp_monitoring/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.contrib import admin

# Register your models here.
Comment on lines +1 to +3
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove unused import or register the SNMP models.

The pipeline is failing due to the unused admin import. Consider either removing the import or registering the SNMP models for admin access.

🔎 Option 1: Remove unused import
-from django.contrib import admin
-
 # Register your models here.
🔎 Option 2: Register models
 from django.contrib import admin
+from .models import SNMPDevice, SNMPMetrics, SNMPInterfaceStats, SNMPDeviceAlert

-# Register your models here.
+
+@admin.register(SNMPDevice)
+class SNMPDeviceAdmin(admin.ModelAdmin):
+    list_display = ('hostname', 'ip_address', 'is_active', 'last_polled')
+    list_filter = ('is_active', 'snmp_version')
+    search_fields = ('hostname', 'ip_address')
+
+
+@admin.register(SNMPDeviceAlert)
+class SNMPDeviceAlertAdmin(admin.ModelAdmin):
+    list_display = ('device', 'alert_type', 'severity', 'created_at', 'acknowledged')
+    list_filter = ('severity', 'acknowledged', 'alert_type')

Would you like me to generate a complete admin registration for all SNMP models?

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
from django.contrib import admin
# Register your models here.
# Register your models here.
🧰 Tools
🪛 GitHub Actions: Lint SNMP Monitoring

[error] 1-1: flake8 F401: 'django.contrib.admin' imported but unused.

🤖 Prompt for AI Agents
In backend/control_center/snmp_monitoring/admin.py lines 1-3, the
django.contrib.admin import is unused and causing the CI failure; either remove
the import line entirely, or replace it by importing your SNMP models (e.g. from
backend.control_center.snmp_monitoring.models import <ModelA>, <ModelB>, ...)
and register them with Django admin (e.g. admin.site.register(<ModelA>) or
register with a custom ModelAdmin) so the import is used; choose one of these
fixes and update the file accordingly.

6 changes: 6 additions & 0 deletions backend/control_center/snmp_monitoring/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class SnmpMonitoringConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'snmp_monitoring'
123 changes: 123 additions & 0 deletions backend/control_center/snmp_monitoring/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# Generated by Django 5.1.14 on 2025-11-24 10:43

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):

initial = True

dependencies = [
('network_device', '0004_alter_networkdevice_operating_system'),
]

operations = [
migrations.CreateModel(
name='SNMPDevice',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(help_text='Human-readable device name', max_length=100)),
('ip_address', models.GenericIPAddressField(db_index=True, help_text='IP address of the SNMP device')),
('vendor', models.CharField(choices=[('mikrotik', 'MikroTik'), ('ubiquiti', 'Ubiquiti'), ('tp_link', 'TP-Link'), ('cisco', 'Cisco'), ('netgear', 'Netgear'), ('d_link', 'D-Link'), ('huawei', 'Huawei'), ('hp', 'HP'), ('other', 'Other')], db_index=True, default='other', help_text='Device vendor/manufacturer', max_length=50)),
('community_string', models.CharField(help_text="SNMP community string (typically 'public' or 'private')", max_length=100)),
('snmp_version', models.CharField(choices=[('2c', 'SNMP v2c')], default='2c', help_text='SNMP version (v3 support to be added later)', max_length=10)),
('port', models.IntegerField(default=161, help_text='SNMP port (default: 161)')),
('polling_interval', models.IntegerField(default=60, help_text='Polling interval in seconds (default: 60)')),
('is_active', models.BooleanField(db_index=True, default=True, help_text='Whether this device should be polled')),
('last_successful_poll', models.DateTimeField(blank=True, help_text='Timestamp of last successful SNMP poll', null=True)),
('last_poll_attempt', models.DateTimeField(blank=True, help_text='Timestamp of last poll attempt (successful or not)', null=True)),
('consecutive_failures', models.IntegerField(default=0, help_text='Number of consecutive polling failures')),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('network_device', models.ForeignKey(blank=True, help_text='Optional link to existing network device', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='snmp_devices', to='network_device.networkdevice')),
],
options={
'ordering': ['-created_at'],
},
),
migrations.CreateModel(
name='SNMPDeviceAlert',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('last_cpu_alert', models.DateTimeField(blank=True, null=True)),
('last_memory_alert', models.DateTimeField(blank=True, null=True)),
('last_disk_alert', models.DateTimeField(blank=True, null=True)),
('last_interface_alert', models.DateTimeField(blank=True, null=True)),
('last_connection_failure_alert', models.DateTimeField(blank=True, null=True)),
('device', models.OneToOneField(help_text='SNMP device for alert tracking', on_delete=django.db.models.deletion.CASCADE, related_name='alert_settings', to='snmp_monitoring.snmpdevice')),
],
),
migrations.CreateModel(
name='SNMPInterfaceStats',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('interface_name', models.CharField(db_index=True, help_text="Interface name (e.g., 'eth0', 'wlan0')", max_length=100)),
('interface_index', models.IntegerField(blank=True, help_text='SNMP interface index (ifIndex)', null=True)),
('bytes_in', models.BigIntegerField(default=0, help_text='Total bytes received on interface')),
('bytes_out', models.BigIntegerField(default=0, help_text='Total bytes transmitted on interface')),
('packets_in', models.BigIntegerField(default=0, help_text='Total packets received on interface')),
('packets_out', models.BigIntegerField(default=0, help_text='Total packets transmitted on interface')),
('errors_in', models.BigIntegerField(default=0, help_text='Input errors on interface')),
('errors_out', models.BigIntegerField(default=0, help_text='Output errors on interface')),
('throughput_mbps', models.FloatField(blank=True, help_text='Calculated throughput in Mbps (if available)', null=True)),
('utilization_percent', models.FloatField(blank=True, help_text='Interface utilization percentage (if link speed known)', null=True)),
('timestamp', models.DateTimeField(auto_now_add=True, db_index=True, help_text='Timestamp when statistics were collected')),
('device', models.ForeignKey(help_text='SNMP device this interface belongs to', on_delete=django.db.models.deletion.CASCADE, related_name='interface_stats', to='snmp_monitoring.snmpdevice')),
],
options={
'ordering': ['-timestamp'],
},
),
migrations.CreateModel(
name='SNMPMetrics',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('cpu_usage', models.FloatField(blank=True, help_text='CPU usage percentage', null=True)),
('memory_usage', models.FloatField(blank=True, help_text='Memory usage percentage', null=True)),
('disk_usage', models.FloatField(blank=True, help_text='Disk usage percentage', null=True)),
('uptime_seconds', models.BigIntegerField(blank=True, help_text='Device uptime in seconds', null=True)),
('timestamp', models.DateTimeField(auto_now_add=True, db_index=True, help_text='Timestamp when metric was collected')),
('device', models.ForeignKey(help_text='SNMP device this metric belongs to', on_delete=django.db.models.deletion.CASCADE, related_name='metrics', to='snmp_monitoring.snmpdevice')),
],
options={
'ordering': ['-timestamp'],
},
),
migrations.AddIndex(
model_name='snmpdevice',
index=models.Index(fields=['ip_address'], name='snmp_monito_ip_addr_22fa3f_idx'),
),
migrations.AddIndex(
model_name='snmpdevice',
index=models.Index(fields=['vendor'], name='snmp_monito_vendor_070617_idx'),
),
migrations.AddIndex(
model_name='snmpdevice',
index=models.Index(fields=['is_active'], name='snmp_monito_is_acti_6fe373_idx'),
),
migrations.AlterUniqueTogether(
name='snmpdevice',
unique_together={('ip_address', 'port')},
),
migrations.AddIndex(
model_name='snmpinterfacestats',
index=models.Index(fields=['device', 'interface_name', 'timestamp'], name='snmp_monito_device__726f5f_idx'),
),
migrations.AddIndex(
model_name='snmpinterfacestats',
index=models.Index(fields=['device', 'timestamp'], name='snmp_monito_device__ff971d_idx'),
),
migrations.AddIndex(
model_name='snmpinterfacestats',
index=models.Index(fields=['timestamp'], name='snmp_monito_timesta_30d54f_idx'),
),
migrations.AddIndex(
model_name='snmpmetrics',
index=models.Index(fields=['device', 'timestamp'], name='snmp_monito_device__c7e7af_idx'),
),
migrations.AddIndex(
model_name='snmpmetrics',
index=models.Index(fields=['timestamp'], name='snmp_monito_timesta_1f76d0_idx'),
),
]
Comment on lines +1 to +123
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Migration structure is correct; consider excluding from line-length linting.

The migration is auto-generated by Django and correctly creates all models with appropriate fields, indexes, and constraints. The schema aligns with models.py and supports the subsequent TimescaleDB migrations.

Pipeline is failing due to multiple E501 (line too long) errors. However, reformatting auto-generated migrations can cause issues with Django's migration auto-detection. Consider:

  1. Excluding migration files from the E501 rule in your flake8 configuration
  2. Or using # noqa: E501 comments if reformatting causes issues
# In .flake8 or setup.cfg
[flake8]
per-file-ignores =
    */migrations/*:E501
🧰 Tools
🪛 GitHub Actions: Lint SNMP Monitoring

[error] 22-22: flake8 E501 line too long (340 > 120 characters).


[error] 23-23: flake8 E501 line too long (140 > 120 characters).


[error] 24-24: flake8 E501 line too long (167 > 120 characters).


[error] 26-26: flake8 E501 line too long (125 > 120 characters).


[error] 27-27: flake8 E501 line too long (130 > 120 characters).


[error] 28-28: flake8 E501 line too long (138 > 120 characters).


[error] 29-29: flake8 E501 line too long (147 > 120 characters).


[error] 30-30: flake8 E501 line too long (125 > 120 characters).


[error] 33-33: flake8 E501 line too long (241 > 120 characters).


[error] 48-48: flake8 E501 line too long (202 > 120 characters).


[error] 55-55: flake8 E501 line too long (136 > 120 characters).


[error] 56-56: flake8 E501 line too long (124 > 120 characters).


[error] 63-63: flake8 E501 line too long (136 > 120 characters).


[error] 64-64: flake8 E501 line too long (150 > 120 characters).


[error] 65-65: flake8 E501 line too long (140 > 120 characters).


[error] 66-66: flake8 E501 line too long (207 > 120 characters).


[error] 80-80: flake8 E501 line too long (135 > 120 characters).


[error] 81-81: flake8 E501 line too long (196 > 120 characters).

🪛 Ruff (0.14.8)

11-13: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


15-123: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

🤖 Prompt for AI Agents
In backend/control_center/snmp_monitoring/migrations/0001_initial.py (lines
1-123): CI is failing on E501 (line too long) for this auto-generated Django
migration; do NOT reformat this file. Fix by updating your flake8 configuration
to ignore E501 for migrations (add a per-file-ignores entry for
*/migrations/*:E501 in .flake8 or setup.cfg) or, if you must change files, add
`# noqa: E501` to the specific long lines in this migration; commit only the
config change (preferred) to stop linting migrations.

Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Migration to alter SNMPMetrics and SNMPInterfaceStats primary keys to include timestamp

from django.db import migrations


class Migration(migrations.Migration):
atomic = False # Altering primary keys typically cannot run inside a transaction

dependencies = [
('snmp_monitoring', '0001_initial'),
]

operations = [
# Alter SNMPMetrics primary key
migrations.RunSQL(
sql="""
-- Drop the automatically created primary key constraint.
ALTER TABLE snmp_monitoring_snmpmetrics DROP CONSTRAINT snmp_monitoring_snmpmetrics_pkey;
-- Create a composite primary key including the partitioning column (timestamp) and the id.
ALTER TABLE snmp_monitoring_snmpmetrics ADD PRIMARY KEY (timestamp, id);
""",
reverse_sql="""
-- Reverse: Drop the composite primary key and restore the original primary key on id.
ALTER TABLE snmp_monitoring_snmpmetrics DROP CONSTRAINT snmp_monitoring_snmpmetrics_pkey;
ALTER TABLE snmp_monitoring_snmpmetrics ADD PRIMARY KEY (id);
""",
),
# Alter SNMPInterfaceStats primary key
migrations.RunSQL(
sql="""
-- Drop the automatically created primary key constraint.
ALTER TABLE snmp_monitoring_snmpinterfacestats DROP CONSTRAINT snmp_monitoring_snmpinterfacestats_pkey;
-- Create a composite primary key including the partitioning column (timestamp) and the id.
ALTER TABLE snmp_monitoring_snmpinterfacestats ADD PRIMARY KEY (timestamp, id);
""",
reverse_sql="""
-- Reverse: Drop the composite primary key and restore the original primary key on id.
ALTER TABLE snmp_monitoring_snmpinterfacestats DROP CONSTRAINT snmp_monitoring_snmpinterfacestats_pkey;
ALTER TABLE snmp_monitoring_snmpinterfacestats ADD PRIMARY KEY (id);
""",
),
]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove trailing blank line to fix linting warning.

The pipeline reports a blank line at end of file (W391).

🔎 Proposed fix

Remove the empty line at line 43.

🧰 Tools
🪛 GitHub Actions: Lint SNMP Monitoring

[warning] 43-43: flake8 W391 blank line at end of file.

🤖 Prompt for AI Agents
In backend/control_center/snmp_monitoring/migrations/0002_alter_primary_keys.py
around line 43, the file ends with a trailing blank line causing lint warning
W391; remove the empty line at the end of the file so there is no blank line
after the last line of code.

Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Migration to convert SNMPMetrics and SNMPInterfaceStats to TimescaleDB hypertables

from django.db import migrations


class Migration(migrations.Migration):
atomic = False # The hypertable conversion must run outside of a transaction.

dependencies = [
('snmp_monitoring', '0002_alter_primary_keys'),
]

operations = [
# Ensure TimescaleDB extension exists before attempting hypertable operations
migrations.RunSQL(
sql="""
CREATE EXTENSION IF NOT EXISTS timescaledb;
""",
reverse_sql=migrations.RunSQL.noop
),
# Convert SNMPMetrics table to TimescaleDB hypertable
# Note: create_default_indexes => FALSE prevents duplicate timestamp index
# (migration 0001 already created all needed indexes including timestamp)
# chunk_time_interval => '1 day' chosen for time-series data
migrations.RunSQL(
sql="""
SELECT create_hypertable(
'snmp_monitoring_snmpmetrics',
'timestamp',
migrate_data => true,
if_not_exists => TRUE,
create_default_indexes => FALSE,
chunk_time_interval => INTERVAL '1 day'
);
""",
reverse_sql="""
-- Safely drop hypertable only if it exists and is actually a hypertable
DO $$
BEGIN
IF EXISTS (
SELECT 1 FROM timescaledb_information.hypertables
WHERE hypertable_name = 'snmp_monitoring_snmpmetrics'
) THEN
PERFORM drop_hypertable('snmp_monitoring_snmpmetrics', IF_EXISTS => TRUE);
END IF;
END $$;
"""
Comment on lines +36 to +47
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

The drop_hypertable() function does not exist in TimescaleDB — this reverse migration will fail.

TimescaleDB drops hypertables using standard PostgreSQL DROP TABLE commands. The reverse SQL uses PERFORM drop_hypertable(...), which is not a valid TimescaleDB function and will cause a runtime error during rollback.

Either:

  1. Use migrations.RunSQL.noop for the reverse (accept that full rollback is not supported)
  2. Replace drop_hypertable() with DROP TABLE IF EXISTS snmp_monitoring_snmpmetrics
🤖 Prompt for AI Agents
In backend/control_center/snmp_monitoring/migrations/0003_make_hypertables.py
around lines 36 to 47, the reverse_sql currently calls PERFORM
drop_hypertable(...) which is not a valid TimescaleDB function and will fail on
rollback; replace the reverse action with either migrations.RunSQL.noop (if you
want to mark rollback as unsupported) or substitute the invalid call with a
plain DROP TABLE IF EXISTS snmp_monitoring_snmpmetrics (optionally preceded by a
check against timescaledb_information if you want to keep the conditional), and
update the RunSQL reverse_sql accordingly so the migration rollback does not
invoke a nonexistent function.

),
# Convert SNMPInterfaceStats table to TimescaleDB hypertable
# Note: create_default_indexes => FALSE prevents duplicate timestamp index
# (migration 0001 already created all needed indexes including timestamp)
# chunk_time_interval => '1 day' chosen for high-frequency data (stats every few seconds)
migrations.RunSQL(
sql="""
SELECT create_hypertable(
'snmp_monitoring_snmpinterfacestats',
'timestamp',
migrate_data => true,
if_not_exists => TRUE,
create_default_indexes => FALSE,
chunk_time_interval => INTERVAL '1 day'
);
""",
reverse_sql="""
-- Safely drop hypertable only if it exists and is actually a hypertable
DO $$
BEGIN
IF EXISTS (
SELECT 1 FROM timescaledb_information.hypertables
WHERE hypertable_name = 'snmp_monitoring_snmpinterfacestats'
) THEN
PERFORM drop_hypertable('snmp_monitoring_snmpinterfacestats', IF_EXISTS => TRUE);
END IF;
END $$;
"""
),
]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove trailing blank line.

Pipeline failure indicates a blank line at end of file (flake8 W391).

🧰 Tools
🪛 GitHub Actions: Lint SNMP Monitoring

[warning] 78-78: flake8 W391 blank line at end of file.

🤖 Prompt for AI Agents
In backend/control_center/snmp_monitoring/migrations/0003_make_hypertables.py
around line 78: there is a trailing blank line at EOF causing flake8 W391;
remove the extra empty line so the file does not end with a blank line and
ensure the file ends with a single newline character (no additional blank lines
after the last code line).

Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Migration to enable compression on SNMPMetrics and SNMPInterfaceStats hypertables

from django.db import migrations


class Migration(migrations.Migration):
atomic = False

dependencies = [
('snmp_monitoring', '0003_make_hypertables'),
]

operations = [
# Enable compression on SNMPMetrics hypertable
migrations.RunSQL(
sql="""
ALTER TABLE snmp_monitoring_snmpmetrics SET (
timescaledb.compress,
timescaledb.compress_segmentby = 'device_id',
timescaledb.compress_orderby = 'timestamp DESC'
);
""",
reverse_sql="""
ALTER TABLE snmp_monitoring_snmpmetrics SET (timescaledb.compress = false);
"""
),

# Add compression policy to compress chunks older than 1 day
migrations.RunSQL(
sql="""
SELECT add_compression_policy(
'snmp_monitoring_snmpmetrics',
INTERVAL '1 day',
if_not_exists => TRUE
);
""",
reverse_sql="""
SELECT remove_compression_policy('snmp_monitoring_snmpmetrics', if_exists => TRUE);
"""
),

# Enable compression on SNMPInterfaceStats hypertable
migrations.RunSQL(
sql="""
ALTER TABLE snmp_monitoring_snmpinterfacestats SET (
timescaledb.compress,
timescaledb.compress_segmentby = 'device_id, interface_name',
timescaledb.compress_orderby = 'timestamp DESC'
);
""",
reverse_sql="""
ALTER TABLE snmp_monitoring_snmpinterfacestats SET (timescaledb.compress = false);
"""
),

# Add compression policy to compress chunks older than 6 hours
# (keeps recent data uncompressed for fast per-second queries)
migrations.RunSQL(
sql="""
SELECT add_compression_policy(
'snmp_monitoring_snmpinterfacestats',
INTERVAL '6 hours',
if_not_exists => TRUE
);
""",
reverse_sql="""
SELECT remove_compression_policy('snmp_monitoring_snmpinterfacestats', if_exists => TRUE);
"""
),
Comment on lines +13 to +69
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Compression strategy looks good; fix whitespace issues to pass linting.

The compression configuration is well-designed:

  • Segmenting by device_id (and interface_name for interface stats) enables efficient per-device queries.
  • The 6-hour policy for high-frequency interface stats keeps recent data uncompressed for fast queries.

However, the pipeline is failing due to whitespace issues on lines 27, 41, 55 (blank lines containing whitespace) and line 71 (trailing blank line). Remove the whitespace from these blank lines.

🧰 Tools
🪛 GitHub Actions: Lint SNMP Monitoring

[warning] 27-27: flake8 W293 blank line contains whitespace.


[warning] 41-41: flake8 W293 blank line contains whitespace.


[warning] 55-55: flake8 W293 blank line contains whitespace.

🪛 Ruff (0.14.8)

13-70: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

🤖 Prompt for AI Agents
In backend/control_center/snmp_monitoring/migrations/0004_enable_compression.py
around lines 13 to 69, there are blank lines containing stray whitespace
(notably at lines 27, 41 and 55) and a trailing blank line at line 71; remove
all spaces/tabs on those blank lines so they are truly empty and delete the
final trailing blank line at EOF to satisfy linting.

]

Empty file.
Loading
Loading