diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 000000000..d28ae8116
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,9 @@
+venv/
+*.pyc
+__pycache__/
+coldfront.db
+coldfront.egg-info
+.gitignore
+script-export-coldfront/
+nginx/
+docs/
diff --git a/.env b/.env
new file mode 100644
index 000000000..6cb233acb
--- /dev/null
+++ b/.env
@@ -0,0 +1,45 @@
+#ColdFront config
+#DEBUG=True
+#DJANGO_SUPERUSER_USERNAME='superuser'
+#DJANGO_SUPERUSER_EMAIL='mail@mail.example'
+#DJANGO_SUPERUSER_PASSWORD='password'
+#SECRET_KEY=klajsfkljasdfkljasdfkljadsfjklads
+#CENTER_NAME="BlaBla Cloud"
+#CENTER_HELP_URL="https://blabla.it/"
+#PLUGIN_SLURM: "True"
+
+#Ldap config:
+#PLUGIN_AUTH_LDAP: "True"
+#AUTH_LDAP_SERVER_URI: "ldaps://ldapserver"
+#AUTH_LDAP_START_TLS: "False"
+#AUTH_LDAP_BIND_DN: 'CN=aa,DC=aa,DC=aa'
+#AUTH_LDAP_BIND_PASSWORD: 'secretpassword'
+#AUTH_LDAP_USER_SEARCH_BASE: 'DC=aa,DC=aa'
+#AUTH_LDAP_GROUP_SEARCH_BASE: 'cn=aa,ou=aa,dc=aa,dc=aa'
+#AUTH_LDAP_MIRROR_GROUPS: "False"
+#AUTH_LDAP_BIND_AS_AUTHENTICATING_USER: "False"
+
+#ONDEMAND_URL: "http://ondemand.hpc"
+
+#Redis config
+#REDIS_HOST=127.0.0.1
+#REDIS_PORT=6379
+#REDIS_DB=0
+
+#Queue configuration
+#Q_CLUSTER_NAME=coldfront
+#Q_CLUSTER_TIMEOUT=60
+
+
+# Database configuration for ColdFront service
+DATABASE_HOST='coldfront_database'
+DATABASE_PORT=3306
+DATABASE_NAME='coldfront'
+DATABASE_USER='coldfront'
+DATABASE_PASSWORD='nomoresecret'
+
+# Environment variables for the ColdFront Worker service
+#DEBUG=True
+#INITIAL_SETUP=False
+#LOAD_TEST_DATA=False
+#REDIS_HOST="coldfront_redis"
diff --git a/.gitignore b/.gitignore
index 8754014b0..b2de8f808 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,6 +18,9 @@ coldfront.db
*.code-workspace
.vscode
db.json
-.env
.devcontainer/*
.bin/*
+docker-clean.bat
+script-export-coldfront/clusters
+script-export-coldfront/ldapserver
+script-export-coldfront/storage
diff --git a/Dockerfile b/Dockerfile
index cb550c69c..e74c22ef4 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,18 +1,26 @@
-FROM python:3.8
+FROM python:3.9-slim-bullseye
+
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
+ && apt-get install -y netcat \
+ && apt-get install -y default-libmysqlclient-dev build-essential \
+ && apt-get install -y libldap2-dev libsasl2-dev \
&& rm -rf /var/lib/apt/lists/*
-WORKDIR /usr/src/app
+RUN pip3 config --user set global.progress_bar off
+
+WORKDIR /opt
COPY requirements.txt ./
-RUN pip3 install -r requirements.txt
+RUN pip3 install --upgrade pip
+RUN pip3 install -r /opt/requirements.txt
COPY . .
+RUN python3 setup.py build
+RUN python3 setup.py install
+RUN pip3 install /opt
+ENV DEBUG="True"
-RUN python3 ./manage.py initial_setup
-RUN python3 ./manage.py load_test_data
+EXPOSE 8000
-ENV DEBUG=True
-EXPOSE 8000
-CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]
+CMD [ "/opt/entrypoint.sh" ]
\ No newline at end of file
diff --git a/coldfront/__init__.py b/coldfront/__init__.py
index 912fb13f1..de6b096c7 100644
--- a/coldfront/__init__.py
+++ b/coldfront/__init__.py
@@ -1,10 +1,14 @@
+from __future__ import absolute_import, unicode_literals
import os
import sys
-__version__ = '1.1.6'
+
+__version__ = '1.1.7'
VERSION = __version__
+
+
def manage():
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "coldfront.config.settings")
from django.core.management import execute_from_command_line
diff --git a/coldfront/config/base.py b/coldfront/config/base.py
index d9f71d972..9cc3a5770 100644
--- a/coldfront/config/base.py
+++ b/coldfront/config/base.py
@@ -21,7 +21,8 @@
SECRET_KEY = ENV.str('SECRET_KEY', default='')
if len(SECRET_KEY) == 0:
SECRET_KEY = get_random_secret_key()
-
+
+SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
#------------------------------------------------------------------------------
# Locale settings
#------------------------------------------------------------------------------
@@ -98,8 +99,14 @@
# Django Q
#------------------------------------------------------------------------------
Q_CLUSTER = {
+ 'name': ENV.str('Q_CLUSTER_NAME', default='coldfront'),
'timeout': ENV.int('Q_CLUSTER_TIMEOUT', default=120),
'retry': ENV.int('Q_CLUSTER_RETRY', default=120),
+ 'redis': {
+ 'host': ENV.str('REDIS_HOST', default='127.0.0.1'),
+ 'port': ENV.int('REDIS_PORT', default=6379),
+ 'db': ENV.int('REDIS_DB', default=0),
+ },
}
diff --git a/coldfront/config/plugins/storage.py b/coldfront/config/plugins/storage.py
new file mode 100644
index 000000000..e42fcd6c8
--- /dev/null
+++ b/coldfront/config/plugins/storage.py
@@ -0,0 +1,8 @@
+from coldfront.config.base import INSTALLED_APPS
+from coldfront.config.env import ENV
+
+INSTALLED_APPS += [
+ 'coldfront.plugins.storage',
+]
+
+#VARIABLE = ENV.str('VARIABLE', default='variable')
diff --git a/coldfront/config/settings.py b/coldfront/config/settings.py
index 1b0f06dd8..76e0dbd08 100644
--- a/coldfront/config/settings.py
+++ b/coldfront/config/settings.py
@@ -22,6 +22,7 @@
'PLUGIN_AUTH_OIDC': 'plugins/openid.py',
'PLUGIN_AUTH_LDAP': 'plugins/ldap.py',
'PLUGIN_LDAP_USER_SEARCH': 'plugins/ldap_user_search.py',
+ 'PLUGIN_STORAGE': 'plugins/storage.py',
}
# This allows plugins to be enabled via environment variables. Can alternatively
diff --git a/coldfront/core/allocation/admin.py b/coldfront/core/allocation/admin.py
index 729c2faad..60b9b183c 100644
--- a/coldfront/core/allocation/admin.py
+++ b/coldfront/core/allocation/admin.py
@@ -1,4 +1,5 @@
import textwrap
+from django import forms
from django.contrib import admin
from django.utils.translation import gettext_lazy as _
@@ -113,11 +114,34 @@ def save_formset(self, request, form, formset, change):
class AttributeTypeAdmin(admin.ModelAdmin):
list_display = ('name', )
+class AllocationAttributeTypeForm(forms.ModelForm):
+ class Meta:
+ model = AllocationAttributeType
+ fields = '__all__'
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.fields['get_usage_command'].widget = forms.TextInput(attrs={'size': 40})
+ self.fields['get_usage_command'].required = False
+
@admin.register(AllocationAttributeType)
class AllocationAttributeTypeAdmin(admin.ModelAdmin):
+ form = AllocationAttributeTypeForm
list_display = ('pk', 'name', 'attribute_type', 'has_usage', 'is_private')
+ fields = ('name', 'attribute_type', 'has_usage', 'get_usage_command', 'is_required', 'is_unique', 'is_private', 'is_changeable')
+
+ class Media:
+ js = ('custom/allocation_attribute_type.js',)
+ def get_fields(self, request, obj=None):
+ fields = super().get_fields(request, obj)
+ return fields
+
+ def save_model(self, request, obj, form, change):
+ if obj.has_usage and not obj.get_usage_command:
+ obj.get_usage_command = '' # Assicurati che questo sia l'effetto desiderato
+ super().save_model(request, obj, form, change)
class AllocationAttributeUsageInline(admin.TabularInline):
model = AllocationAttributeUsage
@@ -361,4 +385,3 @@ class AllocationChangeRequestAdmin(admin.ModelAdmin):
@admin.register(AllocationAttributeChangeRequest)
class AllocationChangeStatusChoiceAdmin(admin.ModelAdmin):
list_display = ('pk', 'allocation_change_request', 'allocation_attribute', 'new_value', )
-
diff --git a/coldfront/core/allocation/migrations/0006_alter_historicalallocation_options_and_more.py b/coldfront/core/allocation/migrations/0006_alter_historicalallocation_options_and_more.py
new file mode 100644
index 000000000..c71378702
--- /dev/null
+++ b/coldfront/core/allocation/migrations/0006_alter_historicalallocation_options_and_more.py
@@ -0,0 +1,86 @@
+# Generated by Django 4.2.11 on 2024-07-22 14:18
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('allocation', '0005_auto_20211117_1413'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='historicalallocation',
+ options={'get_latest_by': ('history_date', 'history_id'), 'ordering': ('-history_date', '-history_id'), 'verbose_name': 'historical allocation', 'verbose_name_plural': 'historical allocations'},
+ ),
+ migrations.AlterModelOptions(
+ name='historicalallocationattribute',
+ options={'get_latest_by': ('history_date', 'history_id'), 'ordering': ('-history_date', '-history_id'), 'verbose_name': 'historical allocation attribute', 'verbose_name_plural': 'historical allocation attributes'},
+ ),
+ migrations.AlterModelOptions(
+ name='historicalallocationattributechangerequest',
+ options={'get_latest_by': ('history_date', 'history_id'), 'ordering': ('-history_date', '-history_id'), 'verbose_name': 'historical allocation attribute change request', 'verbose_name_plural': 'historical allocation attribute change requests'},
+ ),
+ migrations.AlterModelOptions(
+ name='historicalallocationattributetype',
+ options={'get_latest_by': ('history_date', 'history_id'), 'ordering': ('-history_date', '-history_id'), 'verbose_name': 'historical allocation attribute type', 'verbose_name_plural': 'historical allocation attribute types'},
+ ),
+ migrations.AlterModelOptions(
+ name='historicalallocationattributeusage',
+ options={'get_latest_by': ('history_date', 'history_id'), 'ordering': ('-history_date', '-history_id'), 'verbose_name': 'historical allocation attribute usage', 'verbose_name_plural': 'historical allocation attribute usages'},
+ ),
+ migrations.AlterModelOptions(
+ name='historicalallocationchangerequest',
+ options={'get_latest_by': ('history_date', 'history_id'), 'ordering': ('-history_date', '-history_id'), 'verbose_name': 'historical allocation change request', 'verbose_name_plural': 'historical allocation change requests'},
+ ),
+ migrations.AlterModelOptions(
+ name='historicalallocationuser',
+ options={'get_latest_by': ('history_date', 'history_id'), 'ordering': ('-history_date', '-history_id'), 'verbose_name': 'historical allocation user', 'verbose_name_plural': 'historical Allocation User Status'},
+ ),
+ migrations.AddField(
+ model_name='allocationattributetype',
+ name='get_usage_command',
+ field=models.TextField(blank=True, null=True),
+ ),
+ migrations.AddField(
+ model_name='historicalallocationattributetype',
+ name='get_usage_command',
+ field=models.TextField(blank=True, null=True),
+ ),
+ migrations.AlterField(
+ model_name='historicalallocation',
+ name='history_date',
+ field=models.DateTimeField(db_index=True),
+ ),
+ migrations.AlterField(
+ model_name='historicalallocationattribute',
+ name='history_date',
+ field=models.DateTimeField(db_index=True),
+ ),
+ migrations.AlterField(
+ model_name='historicalallocationattributechangerequest',
+ name='history_date',
+ field=models.DateTimeField(db_index=True),
+ ),
+ migrations.AlterField(
+ model_name='historicalallocationattributetype',
+ name='history_date',
+ field=models.DateTimeField(db_index=True),
+ ),
+ migrations.AlterField(
+ model_name='historicalallocationattributeusage',
+ name='history_date',
+ field=models.DateTimeField(db_index=True),
+ ),
+ migrations.AlterField(
+ model_name='historicalallocationchangerequest',
+ name='history_date',
+ field=models.DateTimeField(db_index=True),
+ ),
+ migrations.AlterField(
+ model_name='historicalallocationuser',
+ name='history_date',
+ field=models.DateTimeField(db_index=True),
+ ),
+ ]
diff --git a/coldfront/core/allocation/models.py b/coldfront/core/allocation/models.py
index b89826b2c..ebbb89a05 100644
--- a/coldfront/core/allocation/models.py
+++ b/coldfront/core/allocation/models.py
@@ -404,9 +404,11 @@ class AllocationAttributeType(TimeStampedModel):
is_changeable (bool): indicates whether or not the attribute type is changeable
"""
+
attribute_type = models.ForeignKey(AttributeType, on_delete=models.CASCADE)
name = models.CharField(max_length=50)
has_usage = models.BooleanField(default=False)
+ get_usage_command = models.TextField(blank=True, null=True) # Campo aggiornato
is_required = models.BooleanField(default=False)
is_unique = models.BooleanField(default=False)
is_private = models.BooleanField(default=True)
@@ -416,6 +418,11 @@ class AllocationAttributeType(TimeStampedModel):
def __str__(self):
return '%s' % (self.name)
+ def clean(self):
+ super().clean()
+ if self.has_usage and not self.get_usage_command:
+ raise ValidationError('Command string is required when has_usage is True.')
+
class Meta:
ordering = ['name', ]
diff --git a/coldfront/core/allocation/tasks.py b/coldfront/core/allocation/tasks.py
index e1a37fc05..93afab141 100644
--- a/coldfront/core/allocation/tasks.py
+++ b/coldfront/core/allocation/tasks.py
@@ -1,12 +1,13 @@
import datetime
# import the logging library
import logging
-
-from coldfront.core.allocation.models import (Allocation,
- AllocationStatusChoice)
+import subprocess
+from coldfront.core.allocation.models import (Allocation, AllocationStatusChoice, AllocationAttribute, AllocationAttributeUsage, AllocationAttributeType)
from coldfront.core.user.models import User
from coldfront.core.utils.common import import_from_settings
from coldfront.core.utils.mail import send_email_template
+from django_q.tasks import async_task
+from django_q.models import Schedule
# Get an instance of a logger
logger = logging.getLogger(__name__)
@@ -206,4 +207,85 @@ def send_expiry_emails():
admin_template_context,
EMAIL_SENDER,
[EMAIL_ADMIN_LIST,]
- )
\ No newline at end of file
+ )
+
+def update_allocations_usage():
+ print("Starting update_allocations_usage task...")
+
+ # Find all allocation attribute types that are consumable and have a defined usage command
+ consumable_attributes = AllocationAttributeType.objects.filter(
+ has_usage=True, get_usage_command__isnull=False
+ )
+ print(f"Found {consumable_attributes.count()} consumable attributes with usage commands.")
+
+ for attr_type in consumable_attributes:
+ print(f"Processing attribute type: {attr_type.name}")
+
+ # Find all AllocationAttributes that use this attribute type
+ allocation_attributes = AllocationAttribute.objects.filter(
+ allocation_attribute_type=attr_type
+ )
+ print(f"Found {allocation_attributes.count()} allocation attributes for type {attr_type.name}.")
+
+ for allocation_attr in allocation_attributes:
+ print(f"Processing allocation attribute ID: {allocation_attr.id}")
+
+ # Find the associated Allocation object
+ allocation = Allocation.objects.filter(
+ pk=allocation_attr.pk # Assumes there is a relationship between Allocation and AllocationAttribute
+ ).first()
+
+ if allocation:
+ project = allocation.project
+ print(f"Project for allocation attribute ID {allocation_attr.id}: {project}")
+ else:
+ print(f"Allocation not found for attribute ID {allocation_attr.id}")
+ continue
+
+ # Retrieve the value of the slurm_account_name associated with the allocation
+ slurm_account_name_attr = AllocationAttribute.objects.filter(
+ allocation_attribute_type__name='slurm_account_name',
+ allocation=allocation
+ ).first()
+
+ if slurm_account_name_attr:
+ slurm_account_name = slurm_account_name_attr.value
+ print(f"Slurm account name for allocation ID {allocation_attr.id}: {slurm_account_name}")
+ else:
+ print(f"Slurm account name attribute not found for allocation ID {allocation_attr.id}")
+ continue
+
+ # Retrieve the command to execute
+ command = attr_type.get_usage_command
+ if command:
+ # Replace "%SLURM_ACCOUNT_NAME%" in the command if present
+ command = command.replace("%SLURM_ACCOUNT_NAME%", slurm_account_name)
+ print(f"Executing command: {command}")
+
+ try:
+ # Execute the command
+ result = subprocess.run(command, shell=True, capture_output=True, text=True)
+ print(f"Command output: {result.stdout}")
+
+ # Save the returned value as an integer in the database
+ usage_value = int(result.stdout.strip())
+ print(f"Parsed usage value: {usage_value}")
+
+ # Find or create an AllocationAttributeUsage object
+ usage, created = AllocationAttributeUsage.objects.get_or_create(
+ allocation_attribute=allocation_attr,
+ defaults={'value': usage_value}
+ )
+
+ if created:
+ print(f"Created new AllocationAttributeUsage for attribute ID {allocation_attr.id} with value {usage_value}")
+ else:
+ # Update the value if the object already exists
+ usage.value = usage_value
+ usage.save()
+ print(f"Updated AllocationAttributeUsage for attribute ID {allocation_attr.id} with new value {usage_value}")
+
+ except Exception as e:
+ print(f"Error executing command: {e}")
+
+ print("Finished update_allocations_usage task.")
diff --git a/coldfront/core/allocation/utils.py b/coldfront/core/allocation/utils.py
index b166997b7..0df1770d9 100644
--- a/coldfront/core/allocation/utils.py
+++ b/coldfront/core/allocation/utils.py
@@ -58,4 +58,4 @@ def get_user_resources(user_obj):
def test_allocation_function(allocation_pk):
- print('test_allocation_function', allocation_pk)
+ print('test_allocation_function', allocation_pk)
\ No newline at end of file
diff --git a/coldfront/core/allocation/views.py b/coldfront/core/allocation/views.py
index 216c3531c..050a542cc 100644
--- a/coldfront/core/allocation/views.py
+++ b/coldfront/core/allocation/views.py
@@ -57,7 +57,8 @@
from coldfront.core.allocation.utils import (generate_guauge_data_from_usage,
get_user_resources)
from coldfront.core.project.models import (Project, ProjectUser, ProjectPermission,
- ProjectUserStatusChoice)
+ ProjectUserStatusChoice, ProjectAttribute, ProjectAttributeUsage)
+from coldfront.core.project.views import (ProjectDetailView)
from coldfront.core.resource.models import Resource
from coldfront.core.utils.common import get_domain_url, import_from_settings
from coldfront.core.utils.mail import send_allocation_admin_email, send_allocation_customer_email
@@ -209,6 +210,29 @@ def post(self, request, *args, **kwargs):
allocation_obj.is_changeable = form_data.get('is_changeable')
allocation_obj.status = form_data.get('status')
+ # Simula la creazione della vista per ottenere i dati sugli attributi
+ project_view = ProjectDetailView()
+ project_view.kwargs = {'pk': allocation_obj.project.pk}
+ allocations_data = ProjectDetailView.get_allocation_attributes_usage(request, allocation_obj.project.pk)
+
+ # Use allocations_data as needed
+ total_values = allocations_data['total_values']
+ print(f"Total values of allocation attributes: {total_values}")
+
+ # Update the project's attributes usage
+ project_obj = allocation_obj.project
+ for attribute_name, total_value in total_values.items():
+ try:
+ project_attribute = project_obj.projectattribute_set.get(proj_attr_type__name=attribute_name)
+ project_attribute_usage = project_attribute.projectattributeusage
+ project_attribute_usage.value = total_value
+ project_attribute_usage.save()
+ except ProjectAttribute.DoesNotExist:
+ logger.error(f"Project attribute '{attribute_name}' does not exist.")
+ except ProjectAttributeUsage.DoesNotExist:
+ logger.error(f"Project attribute usage for '{attribute_name}' does not exist.")
+
+
if 'approve' in action:
allocation_obj.status = AllocationStatusChoice.objects.get(name='Active')
elif action == 'deny':
@@ -1842,4 +1866,4 @@ def get(self, request, pk):
messages.success(
request, 'Allocation attribute change request successfully deleted.')
- return HttpResponseRedirect(reverse('allocation-change-detail', kwargs={'pk': allocation_change_pk}))
+ return HttpResponseRedirect(reverse('allocation-change-detail', kwargs={'pk': allocation_change_pk}))
\ No newline at end of file
diff --git a/coldfront/core/grant/migrations/0003_alter_historicalgrant_options_and_more.py b/coldfront/core/grant/migrations/0003_alter_historicalgrant_options_and_more.py
new file mode 100644
index 000000000..b4dfea3bd
--- /dev/null
+++ b/coldfront/core/grant/migrations/0003_alter_historicalgrant_options_and_more.py
@@ -0,0 +1,54 @@
+# Generated by Django 4.2.11 on 2024-07-22 14:18
+
+import coldfront.core.grant.models
+import django.core.validators
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('grant', '0002_auto_20230406_1310'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='historicalgrant',
+ options={'get_latest_by': ('history_date', 'history_id'), 'ordering': ('-history_date', '-history_id'), 'verbose_name': 'historical grant', 'verbose_name_plural': 'historical Grants'},
+ ),
+ migrations.AlterField(
+ model_name='grant',
+ name='direct_funding',
+ field=coldfront.core.grant.models.MoneyField(max_length=100),
+ ),
+ migrations.AlterField(
+ model_name='grant',
+ name='percent_credit',
+ field=coldfront.core.grant.models.PercentField(max_length=100, validators=[django.core.validators.MaxValueValidator(100)]),
+ ),
+ migrations.AlterField(
+ model_name='grant',
+ name='total_amount_awarded',
+ field=coldfront.core.grant.models.MoneyField(max_length=100),
+ ),
+ migrations.AlterField(
+ model_name='historicalgrant',
+ name='direct_funding',
+ field=coldfront.core.grant.models.MoneyField(max_length=100),
+ ),
+ migrations.AlterField(
+ model_name='historicalgrant',
+ name='history_date',
+ field=models.DateTimeField(db_index=True),
+ ),
+ migrations.AlterField(
+ model_name='historicalgrant',
+ name='percent_credit',
+ field=coldfront.core.grant.models.PercentField(max_length=100, validators=[django.core.validators.MaxValueValidator(100)]),
+ ),
+ migrations.AlterField(
+ model_name='historicalgrant',
+ name='total_amount_awarded',
+ field=coldfront.core.grant.models.MoneyField(max_length=100),
+ ),
+ ]
diff --git a/coldfront/core/project/admin.py b/coldfront/core/project/admin.py
index 61dd88422..a6b2f9f2a 100644
--- a/coldfront/core/project/admin.py
+++ b/coldfront/core/project/admin.py
@@ -104,8 +104,10 @@ class AttributeTypeAdmin(admin.ModelAdmin):
@admin.register(ProjectAttributeType)
class ProjectAttributeTypeAdmin(admin.ModelAdmin):
- list_display = ('pk', 'name', 'attribute_type', 'has_usage', 'is_private')
+ list_display = ('pk', 'name', 'attribute_type', 'has_usage', 'is_private', 'is_changeable', 'is_default')
+ class Media:
+ js = ('custom/project_attribute_type.js',)
class ProjectAttributeUsageInline(admin.TabularInline):
model = ProjectAttributeUsage
diff --git a/coldfront/core/project/forms.py b/coldfront/core/project/forms.py
index beeea24e9..2a55f4665 100644
--- a/coldfront/core/project/forms.py
+++ b/coldfront/core/project/forms.py
@@ -1,6 +1,7 @@
import datetime
from django import forms
+from django.db.models import Q
from django.db.models.functions import Lower
from django.shortcuts import get_object_or_404
from ast import Constant
@@ -143,7 +144,7 @@ def __init__(self, *args, **kwargs):
user =(kwargs.get('initial')).get('user')
self.fields['proj_attr_type'].queryset = self.fields['proj_attr_type'].queryset.order_by(Lower('name'))
if not user.is_superuser:
- self.fields['proj_attr_type'].queryset = self.fields['proj_attr_type'].queryset.filter(is_private=False)
+ self.fields['proj_attr_type'].queryset = self.fields['proj_attr_type'].queryset.filter(is_private=False, is_changeable=True)
class ProjectAttributeDeleteForm(forms.Form):
pk = forms.IntegerField(required=False, disabled=True)
diff --git a/coldfront/core/project/migrations/0005_alter_historicalproject_options_and_more.py b/coldfront/core/project/migrations/0005_alter_historicalproject_options_and_more.py
new file mode 100644
index 000000000..1ba1e9444
--- /dev/null
+++ b/coldfront/core/project/migrations/0005_alter_historicalproject_options_and_more.py
@@ -0,0 +1,73 @@
+# Generated by Django 4.2.11 on 2024-07-22 14:18
+
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ('project', '0004_auto_20230406_1133'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='historicalproject',
+ options={'get_latest_by': ('history_date', 'history_id'), 'ordering': ('-history_date', '-history_id'), 'verbose_name': 'historical project', 'verbose_name_plural': 'historical projects'},
+ ),
+ migrations.AlterModelOptions(
+ name='historicalprojectattribute',
+ options={'get_latest_by': ('history_date', 'history_id'), 'ordering': ('-history_date', '-history_id'), 'verbose_name': 'historical project attribute', 'verbose_name_plural': 'historical project attributes'},
+ ),
+ migrations.AlterModelOptions(
+ name='historicalprojectattributetype',
+ options={'get_latest_by': ('history_date', 'history_id'), 'ordering': ('-history_date', '-history_id'), 'verbose_name': 'historical project attribute type', 'verbose_name_plural': 'historical project attribute types'},
+ ),
+ migrations.AlterModelOptions(
+ name='historicalprojectattributeusage',
+ options={'get_latest_by': ('history_date', 'history_id'), 'ordering': ('-history_date', '-history_id'), 'verbose_name': 'historical project attribute usage', 'verbose_name_plural': 'historical project attribute usages'},
+ ),
+ migrations.AlterModelOptions(
+ name='historicalprojectreview',
+ options={'get_latest_by': ('history_date', 'history_id'), 'ordering': ('-history_date', '-history_id'), 'verbose_name': 'historical project review', 'verbose_name_plural': 'historical project reviews'},
+ ),
+ migrations.AlterModelOptions(
+ name='historicalprojectuser',
+ options={'get_latest_by': ('history_date', 'history_id'), 'ordering': ('-history_date', '-history_id'), 'verbose_name': 'historical project user', 'verbose_name_plural': 'historical Project User Status'},
+ ),
+ migrations.AlterField(
+ model_name='historicalproject',
+ name='history_date',
+ field=models.DateTimeField(db_index=True),
+ ),
+ migrations.AlterField(
+ model_name='historicalprojectattribute',
+ name='history_date',
+ field=models.DateTimeField(db_index=True),
+ ),
+ migrations.AlterField(
+ model_name='historicalprojectattributetype',
+ name='history_date',
+ field=models.DateTimeField(db_index=True),
+ ),
+ migrations.AlterField(
+ model_name='historicalprojectattributeusage',
+ name='history_date',
+ field=models.DateTimeField(db_index=True),
+ ),
+ migrations.AlterField(
+ model_name='historicalprojectreview',
+ name='history_date',
+ field=models.DateTimeField(db_index=True),
+ ),
+ migrations.AlterField(
+ model_name='historicalprojectuser',
+ name='history_date',
+ field=models.DateTimeField(db_index=True),
+ ),
+ migrations.AlterUniqueTogether(
+ name='project',
+ unique_together={('title', 'pi')},
+ ),
+ ]
diff --git a/coldfront/core/project/migrations/0006_historicalprojectattributetype_is_default_and_more.py b/coldfront/core/project/migrations/0006_historicalprojectattributetype_is_default_and_more.py
new file mode 100644
index 000000000..3d1b28c8a
--- /dev/null
+++ b/coldfront/core/project/migrations/0006_historicalprojectattributetype_is_default_and_more.py
@@ -0,0 +1,33 @@
+# Generated by Django 4.2.11 on 2024-07-30 09:54
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('project', '0005_alter_historicalproject_options_and_more'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='historicalprojectattributetype',
+ name='is_default',
+ field=models.BooleanField(default=False),
+ ),
+ migrations.AddField(
+ model_name='historicalprojectattributetype',
+ name='is_default_value',
+ field=models.CharField(blank=True, max_length=128, null=True),
+ ),
+ migrations.AddField(
+ model_name='projectattributetype',
+ name='is_default',
+ field=models.BooleanField(default=False),
+ ),
+ migrations.AddField(
+ model_name='projectattributetype',
+ name='is_default_value',
+ field=models.CharField(blank=True, max_length=128, null=True),
+ ),
+ ]
diff --git a/coldfront/core/project/models.py b/coldfront/core/project/models.py
index 45f9470ac..afaaecf66 100644
--- a/coldfront/core/project/models.py
+++ b/coldfront/core/project/models.py
@@ -384,6 +384,7 @@ class ProjectAttributeType(TimeStampedModel):
is_unique (bool): indicates whether or not the value is unique
is_private (bool): indicates whether or not the attribute type is private
is_changeable (bool): indicates whether or not the attribute type is changeable
+ is_default (bool): indicates whether or not the attribute type is default added to all new projects
"""
attribute_type = models.ForeignKey(AttributeType, on_delete=models.CASCADE)
@@ -393,6 +394,8 @@ class ProjectAttributeType(TimeStampedModel):
is_unique = models.BooleanField(default=False)
is_private = models.BooleanField(default=True)
is_changeable = models.BooleanField(default=False)
+ is_default = models.BooleanField(default=False)
+ is_default_value = models.CharField(max_length=128, blank=True, null=True)
history = HistoricalRecords()
def __str__(self):
diff --git a/coldfront/core/project/templates/project/project_detail.html b/coldfront/core/project/templates/project/project_detail.html
index 16183edf4..328425e18 100644
--- a/coldfront/core/project/templates/project/project_detail.html
+++ b/coldfront/core/project/templates/project/project_detail.html
@@ -253,20 +253,22 @@
Attri
{% for attribute in attributes %}
- {% if attribute.proj_attr_type.is_private and is_allowed_to_update_project %}
-
- {{attribute}} |
+ {% if attribute.proj_attr_type.is_changeable and is_allowed_to_update_project %}
+
+ {{attribute}} |
{{attribute.value}} |
Edit |
- {% else %}
-
- {{attribute}} |
+ {% else %}
+
+ {{attribute}} |
{{attribute.value}}
|
- {% if is_allowed_to_update_project %}
- Edit |
+ {% if is_superuser and is_allowed_to_update_project %}
+ Edit |
+ {% else %}
+ |
{% endif %}
{% endif %}
diff --git a/coldfront/core/project/views.py b/coldfront/core/project/views.py
index a71dda349..bd77f4eed 100644
--- a/coldfront/core/project/views.py
+++ b/coldfront/core/project/views.py
@@ -46,6 +46,7 @@
ProjectAttributeUpdateForm)
from coldfront.core.project.models import (Project,
ProjectAttribute,
+ ProjectAttributeType,
ProjectReview,
ProjectReviewStatusChoice,
ProjectStatusChoice,
@@ -95,11 +96,87 @@ def test_func(self):
self.request, 'You do not have permission to view the previous page.')
return False
+ def get_project_attributes_usage(request, pk):
+ project_obj = get_object_or_404(Project, pk=pk)
+
+ if request.user.is_superuser:
+ attributes_with_usage = [
+ attribute for attribute in project_obj.projectattribute_set.all()
+ if hasattr(attribute, 'projectattributeusage')
+ ]
+ else:
+ attributes_with_usage = [
+ attribute for attribute in project_obj.projectattribute_set.filter(proj_attr_type__is_private=False)
+ if hasattr(attribute, 'projectattributeusage')
+ ]
+
+ allocations_data = []
+ invalid_attributes = []
+
+ for attribute in attributes_with_usage:
+ try:
+ allocations_data.append({
+ 'name': attribute.proj_attr_type.name,
+ 'value': float(attribute.value),
+ 'usage': float(attribute.projectattributeusage.value)
+ })
+ except ValueError:
+ logger.error("Attribute '%s' is not an int but has a usage", attribute.proj_attr_type.name)
+ invalid_attributes.append(attribute)
+
+ for a in invalid_attributes:
+ attributes_with_usage.remove(a)
+
+ return {'allocations_data': allocations_data}
+
+ def get_allocation_attributes_usage(request, pk):
+ project_obj = get_object_or_404(Project, pk=pk)
+
+ allocations = Allocation.objects.filter(project=project_obj)
+ allocations_data = []
+ total_values = {} # Dictionario per accumulare i valori totali per ciascun tipo di attributo
+
+ for allocation in allocations:
+ attributes_with_usage = [
+ attribute for attribute in allocation.allocationattribute_set.all()
+ if hasattr(attribute, 'allocationattributeusage')
+ ]
+
+ invalid_attributes = []
+
+ for attribute in attributes_with_usage:
+ try:
+ value = float(attribute.value)
+ usage = float(attribute.allocationattributeusage.value)
+ attribute_name = attribute.allocation_attribute_type.name
+
+ allocations_data.append({
+ 'allocation': allocation.pk,
+ 'name': attribute_name,
+ 'value': value,
+ 'usage': usage
+ })
+
+ if attribute_name not in total_values:
+ total_values[attribute_name] = 0.0
+ total_values[attribute_name] += value
+
+ except ValueError:
+ logger.error("Attribute '%s' is not an int but has a usage", attribute.allocation_attribute_type.name)
+ invalid_attributes.append(attribute)
+
+ for a in invalid_attributes:
+ attributes_with_usage.remove(a)
+
+ return {'allocations_data': allocations_data, 'total_values': total_values}
+
+
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# Can the user update the project?
if self.request.user.is_superuser:
context['is_allowed_to_update_project'] = True
+ context['is_superuser'] = True
elif self.object.projectuser_set.filter(user=self.request.user).exists():
project_user = self.object.projectuser_set.get(
user=self.request.user)
@@ -468,13 +545,23 @@ def form_valid(self, form):
project_obj.save()
self.object = project_obj
- project_user_obj = ProjectUser.objects.create(
+ # Creazione del ProjectUser
+ ProjectUser.objects.create(
user=self.request.user,
project=project_obj,
role=ProjectUserRoleChoice.objects.get(name='Manager'),
status=ProjectUserStatusChoice.objects.get(name='Active')
)
+ # Aggiunta degli attributi predefiniti
+ default_attributes = ProjectAttributeType.objects.filter(is_default=True)
+ for attr_type in default_attributes:
+ ProjectAttribute.objects.create(
+ proj_attr_type=attr_type,
+ project=project_obj,
+ value=attr_type.is_default_value
+ )
+
return super().form_valid(form)
def get_success_url(self):
@@ -1274,7 +1361,7 @@ def test_func(self):
def get_avail_attrs(self, project_obj):
if not self.request.user.is_superuser:
- avail_attrs = ProjectAttribute.objects.filter(project=project_obj, proj_attr_type__is_private=False)
+ avail_attrs = ProjectAttribute.objects.filter(project=project_obj, proj_attr_type__is_private=False, proj_attr_type__is_changeable=True)
else:
avail_attrs = ProjectAttribute.objects.filter(project=project_obj)
avail_attrs_dicts = [
diff --git a/coldfront/core/publication/migrations/0005_alter_historicalpublication_options_and_more.py b/coldfront/core/publication/migrations/0005_alter_historicalpublication_options_and_more.py
new file mode 100644
index 000000000..c571da4ee
--- /dev/null
+++ b/coldfront/core/publication/migrations/0005_alter_historicalpublication_options_and_more.py
@@ -0,0 +1,22 @@
+# Generated by Django 4.2.11 on 2024-07-22 14:18
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('publication', '0004_add_manual_publication_source'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='historicalpublication',
+ options={'get_latest_by': ('history_date', 'history_id'), 'ordering': ('-history_date', '-history_id'), 'verbose_name': 'historical publication', 'verbose_name_plural': 'historical publications'},
+ ),
+ migrations.AlterField(
+ model_name='historicalpublication',
+ name='history_date',
+ field=models.DateTimeField(db_index=True),
+ ),
+ ]
diff --git a/coldfront/core/research_output/migrations/0002_alter_historicalresearchoutput_options_and_more.py b/coldfront/core/research_output/migrations/0002_alter_historicalresearchoutput_options_and_more.py
new file mode 100644
index 000000000..6a39faa8c
--- /dev/null
+++ b/coldfront/core/research_output/migrations/0002_alter_historicalresearchoutput_options_and_more.py
@@ -0,0 +1,22 @@
+# Generated by Django 4.2.11 on 2024-07-22 14:18
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('research_output', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='historicalresearchoutput',
+ options={'get_latest_by': ('history_date', 'history_id'), 'ordering': ('-history_date', '-history_id'), 'verbose_name': 'historical research output', 'verbose_name_plural': 'historical research outputs'},
+ ),
+ migrations.AlterField(
+ model_name='historicalresearchoutput',
+ name='history_date',
+ field=models.DateTimeField(db_index=True),
+ ),
+ ]
diff --git a/coldfront/core/resource/migrations/0003_alter_historicalresource_options_and_more.py b/coldfront/core/resource/migrations/0003_alter_historicalresource_options_and_more.py
new file mode 100644
index 000000000..6d73dd089
--- /dev/null
+++ b/coldfront/core/resource/migrations/0003_alter_historicalresource_options_and_more.py
@@ -0,0 +1,54 @@
+# Generated by Django 4.2.11 on 2024-07-22 14:18
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('resource', '0002_auto_20191017_1141'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='historicalresource',
+ options={'get_latest_by': ('history_date', 'history_id'), 'ordering': ('-history_date', '-history_id'), 'verbose_name': 'historical resource', 'verbose_name_plural': 'historical resources'},
+ ),
+ migrations.AlterModelOptions(
+ name='historicalresourceattribute',
+ options={'get_latest_by': ('history_date', 'history_id'), 'ordering': ('-history_date', '-history_id'), 'verbose_name': 'historical resource attribute', 'verbose_name_plural': 'historical resource attributes'},
+ ),
+ migrations.AlterModelOptions(
+ name='historicalresourceattributetype',
+ options={'get_latest_by': ('history_date', 'history_id'), 'ordering': ('-history_date', '-history_id'), 'verbose_name': 'historical resource attribute type', 'verbose_name_plural': 'historical resource attribute types'},
+ ),
+ migrations.AlterModelOptions(
+ name='historicalresourcetype',
+ options={'get_latest_by': ('history_date', 'history_id'), 'ordering': ('-history_date', '-history_id'), 'verbose_name': 'historical resource type', 'verbose_name_plural': 'historical resource types'},
+ ),
+ migrations.AlterField(
+ model_name='historicalresource',
+ name='history_date',
+ field=models.DateTimeField(db_index=True),
+ ),
+ migrations.AlterField(
+ model_name='historicalresourceattribute',
+ name='history_date',
+ field=models.DateTimeField(db_index=True),
+ ),
+ migrations.AlterField(
+ model_name='historicalresourceattributetype',
+ name='history_date',
+ field=models.DateTimeField(db_index=True),
+ ),
+ migrations.AlterField(
+ model_name='historicalresourcetype',
+ name='history_date',
+ field=models.DateTimeField(db_index=True),
+ ),
+ migrations.AlterField(
+ model_name='resource',
+ name='linked_resources',
+ field=models.ManyToManyField(blank=True, to='resource.resource'),
+ ),
+ ]
diff --git a/coldfront/core/utils/management/commands/add_scheduled_tasks.py b/coldfront/core/utils/management/commands/add_scheduled_tasks.py
index 0f78c8ea7..6606af555 100644
--- a/coldfront/core/utils/management/commands/add_scheduled_tasks.py
+++ b/coldfront/core/utils/management/commands/add_scheduled_tasks.py
@@ -23,3 +23,7 @@ def handle(self, *args, **options):
schedule('coldfront.core.allocation.tasks.send_expiry_emails',
schedule_type=Schedule.DAILY,
next_run=date)
+
+ schedule('coldfront.core.allocation.tasks.update_allocations_usage',
+ schedule_type=Schedule.HOURLY,
+ next_run=date)
\ No newline at end of file
diff --git a/coldfront/plugins/storage/README.md b/coldfront/plugins/storage/README.md
new file mode 100644
index 000000000..2b3342831
--- /dev/null
+++ b/coldfront/plugins/storage/README.md
@@ -0,0 +1,24 @@
+# Storage Plugin for ColdFront
+
+The storage plugin allows to dump the Storage Allocations info (Storage Quota (GB), Storage_Group_Name, Status) to a file or standard output.
+
+## Configuration
+
+
+The plugin checks for Allocations with `Storage Quota (GB)` or `Storage Quota (TB)` and `Storage_Group_Name`.
+If are defined, it exports them to a file in the format
+`Storage Name|Storage_Group_Name|gigabytes|Status`
+
+Example:
+
+ root@localhost:/opt# coldfront storage_dump
+ Found 2 allocations.
+ Processing allocation ID: 2
+ Attributes for allocation 2: {'Storage_Group_Name': 'project_a', 'Storage Quota (GB)': '4000'}
+ Prepared quota: 4000.0 GB for storage group: project_a, Status: Denied
+ Processing allocation ID: 3
+ Attributes for allocation 3: {}
+ Beegfs Fast Storage|project_a|4000|Denied
+
+
+To save the output as a file use `-o output.txt` or `--output_file output.txt`
diff --git a/coldfront/plugins/storage/__init__.py b/coldfront/plugins/storage/__init__.py
new file mode 100644
index 000000000..587f8f46e
--- /dev/null
+++ b/coldfront/plugins/storage/__init__.py
@@ -0,0 +1 @@
+default_app_config = 'coldfront.plugins.storage.apps.StorageConfig'
diff --git a/coldfront/plugins/storage/apps.py b/coldfront/plugins/storage/apps.py
new file mode 100644
index 000000000..085049d83
--- /dev/null
+++ b/coldfront/plugins/storage/apps.py
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class StorageConfig(AppConfig):
+ name = 'coldfront.plugins.storage'
diff --git a/coldfront/plugins/storage/management/__init__.py b/coldfront/plugins/storage/management/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/coldfront/plugins/storage/management/commands/__init__.py b/coldfront/plugins/storage/management/commands/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/coldfront/plugins/storage/management/commands/storage_dump.py b/coldfront/plugins/storage/management/commands/storage_dump.py
new file mode 100644
index 000000000..9adaed2ca
--- /dev/null
+++ b/coldfront/plugins/storage/management/commands/storage_dump.py
@@ -0,0 +1,76 @@
+import logging
+from django.core.management.base import BaseCommand
+from coldfront.core.allocation.models import (
+ Allocation, AllocationAttribute, AllocationStatusChoice
+)
+
+logger = logging.getLogger(__name__)
+
+class Command(BaseCommand):
+ help = 'Export storage quotas for allocations to a file or console.'
+
+ def add_arguments(self, parser):
+ parser.add_argument(
+ '-o', '--output_file',
+ type=str,
+ help='The file to write the storage quotas to. If not provided, output will be printed to the console.'
+ )
+
+ def handle(self, *args, **kwargs):
+ output_file = kwargs.get('output_file')
+
+ # Fetch all allocations
+ allocations = Allocation.objects.all()
+ self.stdout.write(self.style.SUCCESS(f'Found {allocations.count()} allocations.'))
+
+ if allocations.count() == 0:
+ self.stdout.write(self.style.WARNING('No allocations found in the database.'))
+ return
+
+ output = []
+ for allocation in allocations:
+ self.stdout.write(self.style.SUCCESS(f'Processing allocation ID: {allocation.id}'))
+
+ attributes = AllocationAttribute.objects.filter(allocation=allocation)
+ attr_dict = {attr.allocation_attribute_type.name: attr.value for attr in attributes}
+
+ self.stdout.write(self.style.SUCCESS(f'Attributes for allocation {allocation.id}: {attr_dict}'))
+
+ storage_group_name_key = 'Storage_Group_Name'
+ storage_quota_keys = ['Storage Quota (GB)', 'Storage Quota (TB)']
+
+ # Get resource type
+ resource_types = [resource.name for resource in allocation.resources.all()]
+ status_name = allocation.status.name if allocation.status else 'Unknown'
+
+ if (storage_group_name_key in attr_dict and
+ any(key in attr_dict for key in storage_quota_keys)):
+
+ storage_group_name = attr_dict[storage_group_name_key]
+
+ if 'Storage Quota (GB)' in attr_dict:
+ quota = attr_dict['Storage Quota (GB)']
+ unit = 'GB'
+ elif 'Storage Quota (TB)' in attr_dict:
+ quota = float(attr_dict['Storage Quota (TB)']) * 1024 # Convert TB to GB
+ unit = 'TB'
+ else:
+ self.stdout.write(self.style.ERROR(f'No valid storage quota found for allocation ID: {allocation.id}'))
+ continue
+
+ # Convert quota to GB if needed
+ quota_gb = float(quota) if unit == 'GB' else quota
+ for resource_type in resource_types:
+ output.append(f'{resource_type}|{storage_group_name}|{int(quota_gb)}|{status_name}')
+ self.stdout.write(self.style.SUCCESS(f'Prepared quota: {quota_gb} GB for storage group: {storage_group_name}, Status: {status_name}'))
+
+ if output_file:
+ with open(output_file, 'w') as file:
+ file.write('\n'.join(output))
+ self.stdout.write(self.style.SUCCESS(f'Storage quotas successfully exported to {output_file}'))
+ else:
+ # Print the output to the console
+ if output:
+ self.stdout.write(self.style.SUCCESS('\n'.join(output)))
+ else:
+ self.stdout.write(self.style.WARNING('No data to export.'))
diff --git a/coldfront/static/custom/allocation_attribute_type.js b/coldfront/static/custom/allocation_attribute_type.js
new file mode 100644
index 000000000..ad9bbd46e
--- /dev/null
+++ b/coldfront/static/custom/allocation_attribute_type.js
@@ -0,0 +1,22 @@
+// allocation_attribute_type.js
+
+document.addEventListener('DOMContentLoaded', function () {
+ var hasUsageField = document.querySelector('#id_has_usage');
+ var getUsageCommandField = document.querySelector('.field-get_usage_command');
+
+ function toggleGetUsageCommand() {
+ if (hasUsageField.checked) {
+ getUsageCommandField.style.display = 'block';
+ } else {
+ getUsageCommandField.style.display = 'none';
+ }
+ }
+
+ if (hasUsageField && getUsageCommandField) {
+ toggleGetUsageCommand();
+
+ hasUsageField.addEventListener('change', function () {
+ toggleGetUsageCommand();
+ });
+ }
+});
diff --git a/coldfront/static/custom/project_attribute_type.js b/coldfront/static/custom/project_attribute_type.js
new file mode 100644
index 000000000..00a3aac35
--- /dev/null
+++ b/coldfront/static/custom/project_attribute_type.js
@@ -0,0 +1,22 @@
+// project_attribute_type.js
+
+document.addEventListener('DOMContentLoaded', function () {
+ var isDefaultField = document.querySelector('#id_is_default');
+ var isDefaultValueField = document.querySelector('.field-is_default_value');
+
+ function toggleIsDefaultCommand() {
+ if (isDefaultField.checked) {
+ isDefaultValueField.style.display = 'block';
+ } else {
+ isDefaultValueField.style.display = 'none';
+ }
+ }
+
+ if (isDefaultField && isDefaultValueField) {
+ toggleIsDefaultCommand();
+
+ isDefaultField.addEventListener('change', function () {
+ toggleIsDefaultCommand();
+ });
+ }
+});
diff --git a/create_superuser.py b/create_superuser.py
new file mode 100644
index 000000000..a7c9d739e
--- /dev/null
+++ b/create_superuser.py
@@ -0,0 +1,19 @@
+import os
+import django
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "coldfront.config.settings")
+
+django.setup()
+
+from django.contrib.auth.models import User
+
+def create_superuser():
+ # Leggi le variabili di ambiente
+ username = os.getenv('DJANGO_SUPERUSER_USERNAME', 'superuser')
+ email = os.getenv('DJANGO_SUPERUSER_EMAIL', 'mail@mail.example')
+ password = os.getenv('DJANGO_SUPERUSER_PASSWORD', 'password')
+
+ # Crea o aggiorna l'utente super
+ User.objects.create_superuser(username, email, password)
+
+if __name__ == '__main__':
+ create_superuser()
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 000000000..a4f8ffb61
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,112 @@
+version: '3.8'
+
+services:
+
+ nginx:
+ image: nginx:latest
+ container_name: nginx
+ privileged: true
+ ports:
+ - "80:80"
+ - "443:443"
+ environment:
+ SERVER_NAME: "coldfront.hpc"
+ volumes:
+ - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf
+ - ./nginx/server.crt:/etc/nginx/ssl/server.crt
+ - ./nginx/server.key:/etc/nginx/ssl/server.key
+ - static_volume:/opt/static_root
+ depends_on:
+ - coldfront
+
+ coldfront:
+ build:
+ context: .
+ dockerfile: Dockerfile
+ container_name: coldfront
+ privileged: true
+ environment:
+ DEBUG: "False"
+ INITIAL_SETUP: "False" #<----- CHANGE TO FALSE AFTER THE FIRTS RUN
+ LOAD_TEST_DATA: "False"
+ DJANGO_SUPERUSER_USERNAME: "superuser"
+ DJANGO_SUPERUSER_EMAIL: "mail@mail.example"
+ DJANGO_SUPERUSER_PASSWORD: "password"
+ PLUGIN_SLURM: "True"
+ PLUGIN_STORAGE: "True"
+ PLUGIN_AUTH_LDAP: "True"
+ AUTH_LDAP_SERVER_URI: "ldaps://ldapserver"
+ AUTH_LDAP_START_TLS: "False"
+ AUTH_LDAP_BIND_DN: 'CN=aa,DC=aa,DC=aa'
+ AUTH_LDAP_BIND_PASSWORD: 'secretpassword'
+ AUTH_LDAP_USER_SEARCH_BASE: 'DC=aa,DC=aa'
+ AUTH_LDAP_GROUP_SEARCH_BASE: 'cn=aa,ou=aa,dc=aa,dc=aa'
+ AUTH_LDAP_MIRROR_GROUPS: "False"
+ AUTH_LDAP_BIND_AS_AUTHENTICATING_USER: "False"
+ ONDEMAND_URL: "http://ondemand.hpc"
+ SECRET_KEY: "klajsfkljasdfkljasdfkljadsfjklads"
+ DATABASE_HOST: 'coldfront_database'
+ DATABASE_PORT: 3306
+ DATABASE_NAME: 'coldfront'
+ DATABASE_USER: 'coldfront'
+ DATABASE_PASSWORD: 'nomoresecret'
+ DB_URL: "mysql://${DATABASE_USER}:${DATABASE_PASSWORD}@${DATABASE_HOST}:${DATABASE_PORT}/${DATABASE_NAME}"
+ REDIS_HOST: "coldfront_redis"
+ CENTER_NAME: "Blabla Cloud"
+ CENTER_HELP_URL: "https://blabla.it/"
+ PROJECT_ROOT: "/opt"
+ volumes:
+ - static_volume:/opt/static_root
+ - /opt/coldfront/coldfront_dump:/opt/coldfront/coldfront_dump #this folder is used by script-export-coldfront
+ #- ./coldfront.db:/opt/coldfront.db
+ ports:
+ - "8000:8000"
+
+ coldfront_redis:
+ privileged: true
+ image: redis:latest
+ container_name: coldfront_redis
+ command: redis-server
+ ports:
+ - "6379:6379"
+
+ coldfront_worker:
+ privileged: true
+ build:
+ context: .
+ dockerfile: Dockerfile
+ container_name: coldfront_worker
+ command: coldfront qcluster
+ depends_on:
+ - coldfront_redis
+ environment:
+ SECRET_KEY: "klajsfkljasdfkljasdfkljadsfjklads"
+ DATABASE_HOST: 'coldfront_database'
+ DATABASE_PORT: 3306
+ DATABASE_NAME: 'coldfront'
+ DATABASE_USER: 'coldfront'
+ DATABASE_PASSWORD: 'nomoresecret'
+ DB_URL: "mysql://${DATABASE_USER}:${DATABASE_PASSWORD}@${DATABASE_HOST}:${DATABASE_PORT}/${DATABASE_NAME}"
+ REDIS_HOST: "coldfront_redis"
+ Q_CLUSTER_NAME: "coldfront"
+ Q_CLUSTER_TIMEOUT: "60"
+# volumes:
+# - ./coldfront.db:/opt/coldfront.db
+
+
+ coldfront_database:
+ privileged: true
+ image: mariadb:latest
+ container_name: coldfront_database
+ environment:
+ MYSQL_ROOT_PASSWORD: nomoresecret
+ MYSQL_DATABASE: coldfront
+ MYSQL_USER: coldfront
+ MYSQL_PASSWORD: nomoresecret
+ volumes:
+ - mariadb_data:/var/lib/mysql
+
+
+volumes:
+ mariadb_data:
+ static_volume:
\ No newline at end of file
diff --git a/dump.rdb b/dump.rdb
new file mode 100644
index 000000000..76ca5d756
Binary files /dev/null and b/dump.rdb differ
diff --git a/entrypoint.sh b/entrypoint.sh
new file mode 100644
index 000000000..88f4a4ad6
--- /dev/null
+++ b/entrypoint.sh
@@ -0,0 +1,32 @@
+#!/bin/bash
+#set -e
+
+>&2 echo "Waiting for database..."
+DATABASE_PORT=${DATABASE_PORT:-3306}
+while ! echo exit | nc $DATABASE_HOST $DATABASE_PORT; do sleep 5; done
+>&2 echo "Database is up - Starting"
+sleep 10
+
+
+if [[ "$INITIAL_SETUP" == "True" ]]; then
+ echo "yes" | coldfront initial_setup
+ python create_superuser.py
+fi
+
+if [[ "$LOAD_TEST_DATA" == "True" ]]; then
+ echo "yes" | coldfront load_test_data
+fi
+
+coldfront migrate
+
+# Avvia il server Django
+echo "Avviando il server Django..."
+if [[ "$DEBUG" == "True" ]]; then
+ echo "Starting debug server"
+ coldfront runserver 0.0.0.0:8000
+else
+ echo "Starting prod gunicorn server"
+ echo "yes" | python manage.py collectstatic
+ python -m gunicorn coldfront.config.wsgi -b 0.0.0.0:8000 --capture-output
+fi
+
diff --git a/nginx/generate_cert.sh b/nginx/generate_cert.sh
new file mode 100644
index 000000000..dab2fabe9
--- /dev/null
+++ b/nginx/generate_cert.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+# Configura i nomi
+PREFIX="coldfront"
+DOMAIN=".hpc"
+
+# Chiave privata e certificato della CA (non necessario per un certificato auto-firmato semplice)
+openssl genrsa -out server.key 2048
+
+# Genera il certificato SSL auto-firmato
+openssl req -new -x509 -days 365 -key server.key -subj "/C=IT/ST=IT/L=LZ/O=$PREFIX Acme/CN=$PREFIX$DOMAIN" -out server.crt
+
+# Opzionale: verifica il certificato generato
+openssl x509 -in server.crt -text -noout
diff --git a/nginx/nginx.conf b/nginx/nginx.conf
new file mode 100644
index 000000000..755a8b7b9
--- /dev/null
+++ b/nginx/nginx.conf
@@ -0,0 +1,45 @@
+server {
+ listen 80;
+ server_name ${SERVER_NAME};
+
+ location / {
+ proxy_pass http://coldfront:8000;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ }
+
+ location /static/ {
+ autoindex on;
+ alias /opt/static_root/;
+ }
+
+ # Redirect all HTTP requests to HTTPS
+ return 301 https://$host$request_uri;
+}
+
+server {
+ listen 443 ssl;
+ server_name ${SERVER_NAME};
+
+ ssl_certificate /etc/nginx/ssl/server.crt;
+ ssl_certificate_key /etc/nginx/ssl/server.key;
+
+ ssl_protocols TLSv1.2 TLSv1.3;
+ ssl_prefer_server_ciphers on;
+ ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
+
+ location / {
+ proxy_pass http://coldfront:8000;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ }
+
+ location /static/ {
+ autoindex on;
+ alias /opt/static_root/;
+ }
+}
\ No newline at end of file
diff --git a/nginx/server.crt b/nginx/server.crt
new file mode 100644
index 000000000..26d73b9fd
--- /dev/null
+++ b/nginx/server.crt
@@ -0,0 +1,22 @@
+-----BEGIN CERTIFICATE-----
+MIIDkTCCAnmgAwIBAgIUHJzRRuNK1PLXiD+/ndqOlzS9AqYwDQYJKoZIhvcNAQEL
+BQAwWDELMAkGA1UEBhMCSVQxCzAJBgNVBAgMAklUMQswCQYDVQQHDAJMWjEXMBUG
+A1UECgwOY29sZGZyb250IEFjbWUxFjAUBgNVBAMMDWNvbGRmcm9udC5ocGMwHhcN
+MjQwNzI1MTIyODI5WhcNMjUwNzI1MTIyODI5WjBYMQswCQYDVQQGEwJJVDELMAkG
+A1UECAwCSVQxCzAJBgNVBAcMAkxaMRcwFQYDVQQKDA5jb2xkZnJvbnQgQWNtZTEW
+MBQGA1UEAwwNY29sZGZyb250LmhwYzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+AQoCggEBAMWSIXEGMCLbGW9NKJr3ZgrWAqyxhpnCeCFB2ZC1+aiUL3Pf0LIE/cnC
+2CGiCLRT377SwV46NEv7Od6hCyBXns/DHYBYYGFnc+HMUvXUADqt2AF3bSqC0U50
+X6Vv1eVca3H3dQRanUGSRS0epxZc+DkDr4hxxk77d7ovOQmGiuli6QT/L2bXbCUe
+1F4r+5tRzh+qt2xt5mk0x8hFDc73ZWl/hyErs5X4o8dzpF9fJjSIn3twEnbAYTHf
+4rtCDmOMt44H46k9eDnk+dOxHlYclxDQ7E2iG2iC7CaQ/XM+LwVrsJ4sjOnSxIws
+1808n98Z8D3IzW72LQx4KVFPjbZk7OMCAwEAAaNTMFEwHQYDVR0OBBYEFIazsrqX
+AcRDm+qHa9f1dyFSsKUnMB8GA1UdIwQYMBaAFIazsrqXAcRDm+qHa9f1dyFSsKUn
+MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBABhJG9QVmBT/P3AV
+8Vu8xTLzxaX0piiQemblqI21GHSaYxhhDG80N+7yR7AGt6ZovrSrQTBb8L0JMhJ7
+q54EqJNe0Ab9tshuDCpUATeV1RrP6WTE9PAvsO32ceTY4fnvHCLG4bVnKRz/g7Ul
+0o1+i70HT34nVcGbmdhX0RnoMG7tL1ADkNU8vP9cfIMNBPAO0irB0N2yaTYNc/3Z
+taGsjGGEM5WhYxWhGSc/TMneSB9+6rLGRrL65GMFDBKgREGqaI8zDP1gQrrz0v8e
+VCKpbLdzLEOgltHqWYkrMqKLXwx5P086P+OPutjDvPwZgPSaEtNDyje1wp9p1Lvi
+aU/ORl0=
+-----END CERTIFICATE-----
diff --git a/nginx/server.key b/nginx/server.key
new file mode 100644
index 000000000..7f4007f94
--- /dev/null
+++ b/nginx/server.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAxZIhcQYwItsZb00omvdmCtYCrLGGmcJ4IUHZkLX5qJQvc9/Q
+sgT9ycLYIaIItFPfvtLBXjo0S/s53qELIFeez8MdgFhgYWdz4cxS9dQAOq3YAXdt
+KoLRTnRfpW/V5Vxrcfd1BFqdQZJFLR6nFlz4OQOviHHGTvt3ui85CYaK6WLpBP8v
+ZtdsJR7UXiv7m1HOH6q3bG3maTTHyEUNzvdlaX+HISuzlfijx3OkX18mNIife3AS
+dsBhMd/iu0IOY4y3jgfjqT14OeT507EeVhyXENDsTaIbaILsJpD9cz4vBWuwniyM
+6dLEjCzXzTyf3xnwPcjNbvYtDHgpUU+NtmTs4wIDAQABAoIBAQCJ6HDzZfBfxDRC
+3scDNMHDupLvXJOp7HbSMBbfzkZQh+9/oLEaMiW8mgcnouUUip9Zod9cGKC8kMZa
+QmtzzfWK7JVBK29LTl0zNd6KvcrTKtnmXCiVTe8wJkdFQYU6roJJcQP7YAz44lLr
+JcRX9dlGYu45/cEBDMML8T6NCZ8ZPLmWZJmDVhH3LiVUCn33DHa1mtSsc+az8DrX
+ncAl7r7+h7JuvGQBBpRI6CCWDfJYA01y1GVE8h/775yY+z3ArJLQu6hq/J+cnxSb
+tzOiPYnaRHgLm02pbcvP1nzY5elr7mQkSo79HMObzi0gHD0KEHQoT+yrcVE2WF+r
+PdvBs8aRAoGBAOHZdTRatUCeHe5BMOOogNON5Urk6Tz32BDVBghjP5nq91KuBmXd
+GJMkwGFdHccq3sFMnxEbphW/oUWW1Qn0CNfGtrL+ctEdS5y3Rr8cHwVc9YrajCKw
+sWyTFtLZN6gV1ROaFkMpGMQJYCSXlqS3G/LdhVpbXPJJ5GvX5ceq2Nb9AoGBAN/y
+O9NnpL4YCCjoTMv9R1aJH0YN8ecfxtijwW82KHkfm1ly6tHtmT6PZwujyjdq3r43
+lLCVzzNnrm62rPoScyq/gJgOsukjkWYu9k0K0eKlWZrdhDU91lOR/Kn+wFmY1p8P
+cXmrK/URvKJL5u8zgwT4pTJvCghL0ZDi9WcAy0lfAoGABj2vGnJDPfTgmNveUPww
+CyiJpIcs1s12gAiS8RplAIjYqsU6UghJDI/gbRBYqU9K9oZ6gsuDzGgOOdEQNAU1
+VVkdZVbYr2Si2ULLRq6tQMWv11Vwt+iwDnMGF29/NTrdN3xzjwmMsKYdrQUvWX/h
+2tU6QSPzLpLSKBTAOcND2LUCgYEAsL7x0KvgLdexuPd+hRDlGLsoX5Xp8cv2mEzG
+AgvwbYgwh6xXAFBRvjpXzav7kTlbiy06wRO7cIrH9MFgTA37ryVtTJVtRuu3ebpe
+0fdUJ1jxG3TxP0QKv/JcQ564GP6zhN4fZTNfgg/5nO3i2a8CHQ5Q8zkH5fxQ7dTy
+cdUCnGkCgYAzPjV1aeBnsL1r6wJh1epj1mn2mqi44Xublat6o3Y49+MPdPlJVOEa
+nw3N5ZJ8r3r1utuyjok+052/wxaJN4SgEdvZ/eq7uwd4IOCD/vtTN0IIAVQKsuY/
+8XCv2j92peszAR//FF51XDQqIxa5W2VDFhe+NsweWkKxOUUHvBv0rA==
+-----END RSA PRIVATE KEY-----
diff --git a/requirements.txt b/requirements.txt
index efcf7fbbe..4bd0d5b8b 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -6,6 +6,7 @@ certifi==2024.2.2
chardet==5.2.0
charset-normalizer==3.3.2
Django==4.2.11
+django-auth-ldap==4.8.0
django-crispy-forms==2.1
crispy-bootstrap4==2024.1
django-environ==0.11.2
@@ -23,8 +24,11 @@ Faker==24.1.0
fontawesome-free==5.15.4
FormEncode==2.1.0
future==1.0.0
+gunicorn
humanize==4.9.0
idna==3.6
+ldap3==2.9.1
+mysqlclient==2.1.1
pyparsing==3.1.2
python-dateutil==2.9.0.post0
python-memcached==1.62
diff --git a/script-export-coldfront/script.sh b/script-export-coldfront/script.sh
new file mode 100644
index 000000000..19d2628e5
--- /dev/null
+++ b/script-export-coldfront/script.sh
@@ -0,0 +1,498 @@
+#!/bin/bash
+
+#this script has to be launched via crontab.
+#you need 3 files in the same directory: clusters, ldapserver, storage
+#each file must contains:
+#hostname ip ssh_user ssh_password
+
+# Variabili configurabili
+quota_size="10000" # Sostituisci XXXX con il valore desiderato in mb
+home_server_ip="XX.XX.XX.XX" #ricordati di copiare la chiave pubblica ssh su questo server
+
+# quota default per beegfs se non definita in fs/disk su coldfront
+default_fs_disk=20000 # Quota predefinita in MB
+
+# Define the Docker container name
+container_name="coldfront"
+
+# Define the output directory inside the container
+output_dir="/opt/coldfront/coldfront_dump"
+
+# Run the coldfront slurm_dump command inside the Docker container
+docker exec "${container_name}" coldfront slurm_dump -o "${output_dir}"
+
+# Check if the command was successful
+if [[ $? -eq 0 ]]; then
+ echo "The slurm_dump command executed successfully."
+else
+ echo "Error: The slurm_dump command failed."
+fi
+
+# Ottieni la data e l'ora attuali nel formato desiderato (YYYYMMDD_HHMM)
+current_time=$(date +'%Y%m%d_%H%M')
+current_date=$(date +'%Y%m%d')
+
+# Percorso dove sono salvati i file .cfg
+dump_dir="${output_dir}/coldfront_dump_${current_time}"
+
+# Funzione per eseguire SCP per copiare il file .cfg
+copy_cfg_file() {
+ local ip=$1
+ local username=$2
+ local password=$3
+ local cfg_file=$4
+
+ # Copia il file .cfg sul server remoto
+ sshpass -p "${password}" scp -o "StrictHostKeyChecking=no" "${cfg_file}" "${username}@${ip}:/tmp/"
+}
+
+# Funzione per eseguire SSH e caricare il file .cfg con sacctmgr
+load_cfg_with_sacctmgr() {
+ local ip=$1
+ local username=$2
+ local password=$3
+ local cfg_file=$4
+
+ # Carica il file .cfg con sacctmgr
+ sshpass -p "${password}" ssh -o "StrictHostKeyChecking=no" "${username}@${ip}" "echo 'y' | sacctmgr load /tmp/$(basename ${cfg_file})"
+}
+
+# Funzione per eseguire sacctmgr dump e copiare il file dump
+dump_and_copy_file() {
+ local ip=$1
+ local username=$2
+ local password=$3
+ local cluster=$4
+
+ # Nome del file di dump da creare
+ local dump_file="/tmp/${cluster}_dump_data_${current_date}.cfg"
+
+ # Esegui sacctmgr dump sul cluster remoto
+ sshpass -p "${password}" ssh -o "StrictHostKeyChecking=no" "${username}@${ip}" "sacctmgr dump ${cluster} file=${dump_file}"
+
+ # Copia il file dump dal server remoto al server locale
+ sshpass -p "${password}" scp -o "StrictHostKeyChecking=no" "${username}@${ip}:${dump_file}" "${dump_file}"
+}
+
+# Funzione per estrarre solo i comandi sacctmgr dall'output di coldfront slurm_check
+extract_sacctmgr_commands() {
+ local cluster=$1
+
+ coldfront slurm_check -i "/tmp/${cluster}_dump_data_${current_date}.cfg" -s -n 2>&1 | grep "NOOP - Slurm cmd: /usr/bin/" | sed 's/^NOOP - Slurm cmd: \/usr\/bin\///' |
+ while IFS= read -r line; do
+ echo "$line"
+ execute_sacctmgr_commands "$ip" "$username" "$password" "$line"
+ done
+}
+
+# Funzione per eseguire i comandi sacctmgr su un cluster remoto
+execute_sacctmgr_commands() {
+ local ip=$1
+ local username=$2
+ local password=$3
+ shift 3 # Shift per rimuovere ip, username, password e ottenere solo i comandi
+
+ echo "Esecuzione dei comandi sacctmgr su $ip"
+
+ # Itera su ogni comando passato come argomento
+ for cmd in "$@"; do
+ echo "Singolo comando nel for: ${cmd}"
+ # Esegui il comando con sshpass e ssh
+ sshpass -p "${password}" ssh -o "StrictHostKeyChecking=no" "${username}@${ip}" "$cmd"
+
+ # Controlla lo stato di uscita del comando
+ if [[ $? -eq 0 ]]; then
+ echo "Comando \"$cmd\" eseguito con successo su $ip"
+ else
+ echo "Errore durante l'esecuzione del comando \"$cmd\" su $ip"
+ # Puoi decidere di gestire eventuali errori qui
+ fi
+ done
+}
+
+# Funzione per verificare se il percorso esiste su un server remoto
+check_path_on_storage() {
+ local ip=$1
+ local username=$2
+ local password=$3
+ local path=$4
+
+ sshpass -p "${password}" ssh -o "StrictHostKeyChecking=no" "${username}@${ip}" "[[ -d $path ]] && echo 'Path $path exists on $ip' || echo 'Path $path does not exist on $ip'"
+}
+
+# Funzione per gestire le directory e i gruppi degli account
+manage_account_directories_and_groups() {
+ local ip=$1
+ local username=$2
+ local password=$3
+ local account=$4
+ local users=$5
+
+ echo "Connettendosi a ${ip} con utente ${username} per gestire l'account ${account}..."
+
+ # Sostituzione delle variabili esterne al comando SSH
+ local ssh_command=$(cat < /dev/null 2>&1; then
+ echo "Il gruppo ${account} non esiste. Creazione in corso..."
+ #groupadd "${account}"
+ echo "faccio group add ${account}; group commit"
+ cmsh -c "group add ${account}; group commit"
+ echo "Creato gruppo ${account}"
+ else
+ echo "Il gruppo ${account} esiste già."
+ fi
+
+ echo "Verifica della ownership della cartella /mnt/beegfs/proj/${account}..."
+ ####if [[ \$(stat -c %G "/mnt/beegfs/proj/${account}") != "${account}" ]]; then
+ echo "Aggiornamento della ownership della cartella /mnt/beegfs/proj/${account} al gruppo ${account}..."
+ chown :${account} "/mnt/beegfs/proj/${account}"
+ chmod g+wrs,o-rwx "/mnt/beegfs/proj/${account}"
+ echo "Aggiornata ownership della cartella /mnt/beegfs/proj/${account} al gruppo ${account}"
+ ####fi
+ echo "**********************************************"
+ echo "Verifica per cancellazione degli utenti del gruppo ${account}..."
+ echo "**********************************************"
+ current_users=\$(getent group "${account}" | cut -d':' -f4 | sed "s/,/ /g")
+ if [[ -n "\${current_users}" ]]; then
+ echo "current_users = \${current_users}"
+ for user in \${current_users}; do
+ echo "utente ciclo =\$user utenti del progetto = ${users} utenti del gruppo linux = \${current_users}"
+ if ! echo "${users}" | grep -qw "\${user}"; then
+ echo "Rimozione dell'utente \${user} dal gruppo ${account}..."
+ # gpasswd -d "\${user}" "${account}"
+ echo "faccio group removefrom ${account} members \${user}; group commit"
+ cmsh -c "group removefrom ${account} members \${user}; group commit"
+ echo "Rimosso utente \${user} dal gruppo ${account}"
+ fi
+ done
+ fi
+
+ echo "**********************************************"
+ echo "Aggiunta degli utenti ${users} al gruppo ${account}..."
+ echo "**********************************************"
+ utenti="${users}"
+ for user in ${users}; do
+ echo "ecco utente \${user} ... di ${users}"
+ if id -nG "\${user}" ; then
+ if ! id -nG "\${user}" | grep -qw "${account}"; then
+ echo "Aggiunta dell utente \${user} al gruppo ${account}..."
+ #usermod -aG "${account}" "\${user}"
+ echo "faccio group append ${account} members \${user}; group commit"
+ cmsh -c "group append ${account} members \${user}; group commit"
+ echo "Aggiunto utente \${user} al gruppo ${account}"
+ fi
+ fi
+ done
+ echo "...lancio il comando..."
+EOF
+)
+
+###echo "[command]\n ${ssh_command}" >> /tmp/scripts-command.log
+
+ # Esecuzione del comando SSH con il comando costruito
+ sshpass -p "${password}" ssh -o "StrictHostKeyChecking=no" "${username}@${ip}" "${ssh_command}"
+}
+
+# Funzione per controllare i file di configurazione e gestire directory e gruppi
+check_clusters_and_cfg_files() {
+ for cfg_file in "${dump_dir}"/*.cfg; do
+ file_name=$(basename "${cfg_file}" .cfg)
+ if [[ -n ${hosts_map[$file_name]} ]]; then
+ echo "Contenuto del file di configurazione ${cfg_file}:"
+ cat "${cfg_file}"
+
+ declare -A account_users_map
+ while IFS= read -r line; do
+ if [[ $line == Account* ]]; then
+ account=$(echo "$line" | grep -oP "(?<=Account - ')[^']+")
+ fs_disk=$(echo "$line" | grep -oP "(?<=fs/disk=)[^:]+")
+ account_users_map["$account"]=""
+ if [[ -n $fs_disk ]]; then
+ echo "Account: $account, Quota storage (fs/disk): $fs_disk"
+ else
+ echo "Account: $account, Quota storage (fs/disk): non definita"
+ fi
+ elif [[ $line == Parent* ]]; then
+ parent=$(echo "$line" | grep -oP "(?<=Parent - ')[^']+")
+ elif [[ $line == User* ]]; then
+ user=$(echo "$line" | grep -oP "(?<=User - ')[^']+")
+ if [[ -n ${account_users_map[$parent]} ]]; then
+ account_users_map["$parent"]="${account_users_map[$parent]} $user"
+ else
+ account_users_map["$parent"]="$user"
+ fi
+ fi
+ done < "${cfg_file}"
+
+ for account in "${!account_users_map[@]}"; do
+ users=${account_users_map[$account]}
+ if [[ -n $users ]]; then
+ echo "Account: $account, Users: $users"
+ ####manage_account_directories_and_groups "${storage_map["ip"]}" "${storage_map["username"]}" "${storage_map["password"]}" "$account" "$users"
+ manage_account_directories_and_groups "${ldap_map["ip"]}" "${ldap_map["username"]}" "${ldap_map["password"]}" "$account" "$users"
+ fi
+ done
+
+ else
+ echo "Nessuna corrispondenza trovata per $file_name nel file hosts"
+ fi
+ done
+}
+
+
+# Funzione per controllare i file di configurazione e gestire directory e gruppi
+check_users_for_quotas() {
+ for cfg_file in "${dump_dir}"/*.cfg; do
+ file_name=$(basename "${cfg_file}" .cfg)
+ if [[ -n ${hosts_map[$file_name]} ]]; then
+ echo "Contenuto del file di configurazione ${cfg_file}:"
+ cat "${cfg_file}"
+
+ declare -A account_users_map
+#leggo il file intero
+ readarray -t linee < ${cfg_file}
+ for line in "${linee[@]}"; do
+ if [[ $line == Account* ]]; then
+ account=$(echo "$line" | grep -oP "(?<=Account - ')[^']+")
+ fs_disk=$(echo "$line" | grep -oP "(?<=fs/disk=)[^:]+")
+ account_users_map["$account"]=""
+ if [[ -n $fs_disk ]]; then
+ echo "Account: $account, Quota storage (fs/disk): $fs_disk"
+ set_beegfs_quota "${storage_map["ip"]}" "${storage_map["username"]}" "${storage_map["password"]}" "${account}" "${fs_disk}"
+ else
+ echo "Account: $account, Quota storage (fs/disk): non definita. Defaulting to ${default_fs_disk}MB"
+ set_beegfs_quota "${storage_map["ip"]}" "${storage_map["username"]}" "${storage_map["password"]}" "${account}"
+ fi
+ elif [[ $line == Parent* ]]; then
+ parent=$(echo "$line" | grep -oP "(?<=Parent - ')[^']+")
+ elif [[ $line == User* ]]; then
+ user=$(echo "$line" | grep -oP "(?<=User - ')[^']+")
+ if [[ -n ${account_users_map[$parent]} ]]; then
+ account_users_map["$parent"]="${account_users_map[$parent]} $user"
+ else
+ account_users_map["$parent"]="$user"
+ fi
+ fi
+ done
+
+
+ for account in "${!account_users_map[@]}"; do
+ users=${account_users_map[$account]}
+ for user in ${users}; do
+ user_id=$(get_user_id "${storage_map["ip"]}" "${storage_map["username"]}" "${storage_map["password"]}" "$account" "$user")
+ echo "Id for user ${user} is ${user_id}"
+ set_user_quota_on_remote_server "$home_server_ip" "$user_id" "$quota_size"
+ done
+ done
+
+ else
+ echo "Nessuna corrispondenza trovata per $file_name nel file hosts"
+ fi
+ done
+}
+
+read_ldap_info() {
+ while read -r ldap_name ldap_ip ldap_username ldap_password; do
+ ldap_map["ip"]=$ldap_ip
+ ldap_map["username"]=$ldap_username
+ ldap_map["password"]=$ldap_password
+ done < /opt/script-export-coldfront/ldapserver
+}
+
+
+
+read_storage_info() {
+ while read -r storage_name storage_ip storage_username storage_password; do
+ storage_map["ip"]=$storage_ip
+ storage_map["username"]=$storage_username
+ storage_map["password"]=$storage_password
+ done < /opt/script-export-coldfront/storage
+}
+
+# Funzione per recuperare l'ID utente su Linux
+get_user_id() {
+ local ip=$1
+ local username=$2
+ local password=$3
+ local account=$4
+ local user=$5
+
+
+ # Sostituzione delle variabili esterne al comando SSH
+ local ssh_command=$(cat <