Merge pull request 'develop' (#39) from develop into main
oggui-debian-package/pipeline/head This commit looks good Details
testing/ogGui-multibranch/pipeline/head There was a failure building this commit Details
oggui-debian-package/pipeline/tag This commit looks good Details

Reviewed-on: #39
pull/40/head 0.22.0
Manuel Aranda Rosales 2025-09-03 08:32:13 +02:00
commit c5c670cb87
16 changed files with 701 additions and 173 deletions

View File

@ -1,5 +1,13 @@
# Changelog
## [0.22.0] - 2025-09-03
### Added
- Nueva ux a la hora de gestionar tareas programadas.
- En modificar imagen git, nueva opcion para "forzar push".
### Improved
- Nuevo manejo de errores en el modulo de ogRepository.
---
## [0.21.0] - 2025-08-28
### Added
- Se ha incluido una integracion con el agente, para generar los scripts que se llaman al particionar.

View File

@ -8,7 +8,7 @@
<mat-form-field appearance="fill" class="w-full">
<mat-label>Repetición</mat-label>
<mat-select formControlName="recurrenceType">
<mat-option *ngFor="let type of recurrenceTypes" [value]="type">{{ type | titlecase }}</mat-option>
<mat-option *ngFor="let opt of recurrenceTypes" [value]="opt.value">{{ opt.label }}</mat-option>
</mat-select>
</mat-form-field>

View File

@ -14,7 +14,10 @@ export class CreateTaskScheduleComponent implements OnInit{
form: FormGroup;
baseUrl: string;
apiUrl: string;
recurrenceTypes = ['none', 'custom'];
recurrenceTypes = [
{ value: 'none', label: 'Sin repetición' },
{ value: 'custom', label: 'Repetición personalizada' }
];
weekDays: string[] = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'];
isSingleDateSelected: boolean = true;
monthsList: string[] = [
@ -108,12 +111,27 @@ export class CreateTaskScheduleComponent implements OnInit{
}
formatExecutionTime(time: string | Date): string {
const date = (time instanceof Date) ? time : new Date(time);
if (isNaN(date.getTime())) {
console.error('Invalid execution time:', time);
if (typeof time === 'string') {
const hhmmMatch = time.match(/^\d{2}:\d{2}/);
if (hhmmMatch) {
return hhmmMatch[0];
}
const parsed = new Date(time);
if (!isNaN(parsed.getTime())) {
const hours = String(parsed.getHours()).padStart(2, '0');
const minutes = String(parsed.getMinutes()).padStart(2, '0');
return `${hours}:${minutes}`;
}
return '';
}
return date.toISOString().substring(11, 16);
if (time instanceof Date && !isNaN(time.getTime())) {
const hours = String(time.getHours()).padStart(2, '0');
const minutes = String(time.getMinutes()).padStart(2, '0');
return `${hours}:${minutes}`;
}
return '';
}
convertDateToLocalISO(date: Date): string {
@ -191,7 +209,7 @@ export class CreateTaskScheduleComponent implements OnInit{
private calculateNextExecutionAndCount(): void {
const recurrence = this.form.get('recurrenceType')?.value;
const time = this.form.get('executionTime')?.value;
if (recurrence === 'none') {
const execDate = this.form.get('executionDate')?.value;
if (execDate && time) {
@ -200,31 +218,102 @@ export class CreateTaskScheduleComponent implements OnInit{
this.nextExecutionDate.setHours(parseInt(hours), parseInt(minutes), 0, 0);
this.executionCount = 1;
}
} else {
const startDate = this.form.get('recurrenceDetails.initDate')?.value;
const endDate = this.form.get('recurrenceDetails.endDate')?.value;
const days = Object.keys(this.selectedDays).filter(day => this.selectedDays[day]);
const months = Object.keys(this.selectedMonths).filter(month => this.selectedMonths[month]);
if (startDate && endDate && days.length > 0 && months.length > 0) {
// Calcular próxima ejecución (simplificado)
this.nextExecutionDate = new Date(startDate);
const [hours, minutes] = time.split(':');
this.nextExecutionDate.setHours(parseInt(hours), parseInt(minutes), 0, 0);
// Calcular número aproximado de ejecuciones
this.executionCount = this.calculateExecutionCount(startDate, endDate, days.length, months.length);
}
return;
}
const startDateValue = this.form.get('recurrenceDetails.initDate')?.value;
const endDateValue = this.form.get('recurrenceDetails.endDate')?.value;
const selectedDayNames = Object.keys(this.selectedDays).filter(day => this.selectedDays[day]);
const selectedMonthNames = Object.keys(this.selectedMonths).filter(month => this.selectedMonths[month]);
if (!startDateValue || !endDateValue || selectedDayNames.length === 0 || selectedMonthNames.length === 0) {
this.nextExecutionDate = null;
this.executionCount = 0;
return;
}
const startDate = new Date(startDateValue);
const endDate = new Date(endDateValue);
const selectedDayIndices = new Set<number>(selectedDayNames.map(name => this.getDayIndexFromName(name)));
const selectedMonthIndices = new Set<number>(selectedMonthNames.map(name => this.getMonthIndexFromName(name)));
// Conteo exacto de ejecuciones en el rango [startDate, endDate]
this.executionCount = this.calculateExecutionCountExact(startDate, endDate, selectedDayIndices, selectedMonthIndices);
// Próxima ejecución >= ahora dentro del rango y criterios
this.nextExecutionDate = this.findNextExecutionDate(startDate, endDate, time, selectedDayIndices, selectedMonthIndices);
}
private calculateExecutionCount(startDate: Date, endDate: Date, daysCount: number, monthsCount: number): number {
const start = new Date(startDate);
private calculateExecutionCountExact(startDate: Date, endDate: Date, selectedDays: Set<number>, selectedMonths: Set<number>): number {
const cursor = new Date(startDate);
cursor.setHours(0, 0, 0, 0);
const end = new Date(endDate);
const daysDiff = Math.ceil((end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24));
// Aproximación: días seleccionados por semana * semanas * meses activos
return Math.ceil((daysCount / 7) * (daysDiff / 7) * (monthsCount / 12));
end.setHours(23, 59, 59, 999);
let count = 0;
while (cursor <= end) {
if (selectedMonths.has(cursor.getMonth()) && selectedDays.has(cursor.getDay())) {
count += 1;
}
cursor.setDate(cursor.getDate() + 1);
}
return count;
}
private findNextExecutionDate(startDate: Date, endDate: Date, time: string, selectedDays: Set<number>, selectedMonths: Set<number>): Date | null {
const now = new Date();
const startCandidate = new Date(Math.max(new Date(startDate).setHours(0, 0, 0, 0), new Date(now).setHours(0, 0, 0, 0)));
const endLimit = new Date(endDate);
endLimit.setHours(23, 59, 59, 999);
const [hours, minutes] = (time || '00:00').split(':');
const candidate = new Date(startCandidate);
while (candidate <= endLimit) {
if (selectedMonths.has(candidate.getMonth()) && selectedDays.has(candidate.getDay())) {
const candidateDateTime = new Date(candidate);
candidateDateTime.setHours(parseInt(hours), parseInt(minutes), 0, 0);
if (candidateDateTime >= now) {
return candidateDateTime;
}
}
candidate.setDate(candidate.getDate() + 1);
}
return null;
}
private getDayIndexFromName(dayName: string): number {
const mapping: { [key: string]: number } = {
sunday: 0,
monday: 1,
tuesday: 2,
wednesday: 3,
thursday: 4,
friday: 5,
saturday: 6
};
return mapping[dayName.toLowerCase()] ?? -1;
}
private getMonthIndexFromName(monthName: string): number {
const mapping: { [key: string]: number } = {
january: 0,
february: 1,
march: 2,
april: 3,
may: 4,
june: 5,
july: 6,
august: 7,
september: 8,
october: 9,
november: 10,
december: 11
};
return mapping[monthName.toLowerCase()] ?? -1;
}
formatDate(date: string | Date): string {

View File

@ -69,48 +69,287 @@ table {
}
.mat-elevation-z8 {
box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.2);
border-radius: 12px;
overflow: hidden;
box-shadow: none;
}
.paginator-container {
.mat-header-cell {
background-color: #f8f9fa;
font-weight: 600;
color: #495057;
padding: 16px 8px;
border-bottom: 2px solid #e9ecef;
}
.mat-cell {
padding: 16px 8px;
border-bottom: 1px solid #f1f3f4;
}
.recurrence-type-container {
display: flex;
justify-content: end;
margin-bottom: 30px;
justify-content: center;
}
mat-spinner {
margin: 0 auto;
align-self: center;
}
.subnets-button-row {
.recurrence-chip {
display: flex;
gap: 15px;
align-items: center;
padding: 7px 12px;
border-radius: 20px;
min-width: 200px;
transition: all 0.3s ease;
margin: 4px 0;
}
.recurrence-icon {
margin-right: 12px;
font-size: 20px;
width: 20px;
height: 20px;
}
.recurrence-content {
flex: 1;
}
.recurrence-label {
font-weight: 600;
font-size: 14px;
margin-bottom: 4px;
}
.recurrence-details {
font-size: 12px;
opacity: 0.8;
line-height: 1.2;
}
/* Colores para diferentes tipos de recurrencia */
.recurrence-none {
background-color: #667eea;
color: white;
}
.recurrence-daily {
background-color: #f093fb;
color: white;
}
.recurrence-weekly {
background-color: #4facfe;
color: white;
}
.recurrence-monthly {
background-color: #43e97b;
color: white;
}
.recurrence-yearly {
background-color: #fa709a;
color: white;
}
.recurrence-custom {
background-color: #ff9a9e;
color: #333;
}
/* Estilos para el campo executionTime */
.time-display {
display: flex;
align-items: center;
justify-content: center;
padding: 8px 12px;
background-color: #667eea;
color: white;
border-radius: 16px;
min-width: 100px;
}
.time-icon {
margin-right: 8px;
font-size: 16px;
width: 16px;
height: 16px;
}
.time-value {
font-weight: 600;
font-size: 14px;
}
/* Estilos para el campo nextExecution */
.next-execution {
display: flex;
align-items: center;
justify-content: center;
padding: 8px 12px;
background-color: #4facfe;
color: white;
border-radius: 16px;
min-width: 120px;
}
.execution-icon {
margin-right: 8px;
font-size: 16px;
width: 16px;
height: 16px;
}
/* Estilos para el campo daysOfWeek */
.days-display {
text-align: center;
}
.days-header {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 8px;
color: #495057;
}
.days-icon {
margin-right: 6px;
font-size: 16px;
width: 16px;
height: 16px;
color: #4facfe;
}
.days-count {
font-size: 12px;
font-weight: 500;
}
.days-chips {
display: flex;
flex-wrap: wrap;
gap: 4px;
justify-content: center;
}
.day-chip {
background-color: #4facfe;
color: white;
padding: 4px 8px;
border-radius: 12px;
font-size: 11px;
font-weight: 600;
min-width: 32px;
text-align: center;
}
/* Estilos para el campo months */
.months-display {
text-align: center;
}
.months-header {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 8px;
color: #495057;
}
.months-icon {
margin-right: 6px;
font-size: 16px;
width: 16px;
height: 16px;
color: #43e97b;
}
.months-count {
font-size: 12px;
font-weight: 500;
}
.months-chips {
display: flex;
flex-wrap: wrap;
gap: 4px;
justify-content: center;
}
.month-chip {
background-color: #43e97b;
color: white;
padding: 4px 8px;
border-radius: 12px;
font-size: 11px;
font-weight: 600;
min-width: 32px;
text-align: center;
}
/* Estilos para el campo enabled */
.status-container {
display: flex;
justify-content: center;
}
.status-indicator {
display: flex;
align-items: center;
padding: 8px 16px;
border-radius: 20px;
font-weight: 600;
font-size: 14px;
min-width: 100px;
justify-content: center;
}
.status-active {
background-color: #43e97b;
color: white;
}
.status-inactive {
background-color: #ff9a9e;
color: #333;
}
.status-icon {
margin-right: 8px;
font-size: 16px;
width: 16px;
height: 16px;
}
/* Estilos para datos no definidos */
.no-data {
text-align: center;
padding: 8px;
}
.no-data-text {
color: #6c757d;
font-style: italic;
font-size: 12px;
}
/* Estilos para el header de acciones */
.header-actions {
margin-bottom: 20px;
display: flex;
justify-content: flex-end;
margin-bottom: 20px;
padding: 16px;
background: #f8f9fa;
border-radius: 8px;
}
.action-button {
color: white;
border: none;
padding: 12px 24px;
font-weight: 600;
cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
padding: 12px 24px;
color: white;
border: none;
font-weight: 500;
cursor: pointer;
transition: background-color 0.2s ease;
}
.action-button:hover {
background: #1565c0;
transition: all 0.3s ease;
}
.action-button mat-icon {
@ -119,90 +358,60 @@ mat-spinner {
height: 20px;
}
.create-button {
color: white;
border: none;
padding: 12px 24px;
font-weight: 500;
}
.create-button mat-icon {
margin-right: 8px;
}
/* Estilos para el contenedor de búsqueda */
.search-container {
display: flex;
gap: 16px;
margin: 20px 0;
margin-bottom: 20px;
flex-wrap: wrap;
}
.search-string {
flex: 1;
min-width: 250px;
}
/* Estilos para los chips de días y meses */
.days-display, .months-display {
/* Estilos para el paginador */
.paginator-container {
margin-top: 20px;
display: flex;
flex-wrap: wrap;
gap: 4px;
justify-content: center;
}
.day-chip, .month-chip {
background: #e3f2fd;
color: #1976d2;
padding: 4px 8px;
border-radius: 12px;
font-size: 0.75rem;
font-weight: 500;
border: 1px solid #bbdefb;
/* Estilos para los botones de acción en la tabla */
.mat-column-actions .mat-cell {
text-align: center;
}
/* Estilos para la próxima ejecución */
.next-execution {
display: flex;
align-items: center;
gap: 8px;
color: #555;
.mat-column-actions button {
margin: 0 4px;
transition: all 0.3s ease;
}
.execution-icon {
font-size: 18px;
color: #2196f3;
.mat-column-actions button:hover {
transform: scale(1.1);
}
/* Mejoras en la tabla */
.mat-table {
border-radius: 8px;
overflow: hidden;
}
.mat-header-cell {
background: #f5f5f5;
font-weight: 600;
color: #333;
}
.mat-row:hover {
background: #f8f9fa;
}
/* Responsive */
/* Responsive design */
@media (max-width: 768px) {
.recurrence-chip {
min-width: 160px;
padding: 10px 12px;
}
.recurrence-label {
font-size: 13px;
}
.recurrence-details {
font-size: 11px;
}
.search-container {
flex-direction: column;
}
.search-string {
min-width: auto;
width: 100%;
}
.header-actions {
justify-content: center;
}
.create-button {
width: 100%;
min-width: 100%;
}
}

View File

@ -44,24 +44,22 @@
<td mat-cell *matCellDef="let schedule">
<ng-container *ngIf="column.columnDef === 'recurrenceType'">
<mat-chip style="padding: 10px; margin: 5px;">
<ng-container *ngIf="column.cell(schedule) === 'none'; else scheduledTemplate">
No programado
<div style="font-size: 12px;">
{{ schedule.executionDate | date }}
<div class="recurrence-type-container">
<div class="recurrence-chip" [ngClass]="'recurrence-' + column.cell(schedule).type">
<mat-icon class="recurrence-icon">{{ column.cell(schedule).icon }}</mat-icon>
<div class="recurrence-content">
<div class="recurrence-label">{{ column.cell(schedule).label }}</div>
<div class="recurrence-details">{{ column.cell(schedule).details }}</div>
</div>
</ng-container>
<ng-template #scheduledTemplate>
Programado
<div style="font-size: 12px;">
{{ schedule.recurrenceDetails?.initDate | date }} → {{ schedule.recurrenceDetails?.endDate | date}}
</div>
</ng-template>
</mat-chip>
</div>
</div>
</ng-container>
<ng-container *ngIf="column.columnDef === 'executionTime'">
{{ schedule.executionTime | date: 'HH:mm' }}
<div class="time-display">
<mat-icon class="time-icon">schedule</mat-icon>
<span class="time-value">{{ schedule.executionTime | date: 'HH:mm' }}</span>
</div>
</ng-container>
<ng-container *ngIf="column.columnDef === 'nextExecution'">
@ -72,16 +70,26 @@
</ng-container>
<ng-container *ngIf="column.columnDef === 'daysOfWeek'">
<div class="days-display">
<span *ngFor="let day of schedule.recurrenceDetails?.daysOfWeek"
class="day-chip">{{ day | slice:0:3 }}</span>
<div class="days-display" *ngIf="column.cell(schedule)">
<div class="days-chips">
<span *ngFor="let day of column.cell(schedule).days"
class="day-chip">{{ day }}</span>
</div>
</div>
<div class="no-data" *ngIf="!column.cell(schedule)">
<span class="no-data-text">No definido</span>
</div>
</ng-container>
<ng-container *ngIf="column.columnDef === 'months'">
<div class="months-display">
<span *ngFor="let month of schedule.recurrenceDetails?.months"
class="month-chip">{{ month | slice:0:3 }}</span>
<div class="months-display" *ngIf="column.cell(schedule)">
<div class="months-chips">
<span *ngFor="let month of column.cell(schedule).months"
class="month-chip">{{ month }}</span>
</div>
</div>
<div class="no-data" *ngIf="!column.cell(schedule)">
<span class="no-data-text">No definido</span>
</div>
</ng-container>
@ -92,14 +100,12 @@
</ng-container>
<ng-container *ngIf="column.columnDef === 'enabled'">
<mat-chip [color]="schedule.enabled ? 'accent' : 'warn'" selected>
<ng-container *ngIf="schedule.enabled">
Activo
</ng-container>
<ng-container *ngIf="!schedule.enabled">
Inactivo
</ng-container>
</mat-chip>
<div class="status-container">
<div class="status-indicator" [ngClass]="schedule.enabled ? 'status-active' : 'status-inactive'">
<mat-icon class="status-icon">{{ schedule.enabled ? 'check_circle' : 'cancel' }}</mat-icon>
<span class="status-text">{{ schedule.enabled ? 'Activo' : 'Inactivo' }}</span>
</div>
</div>
</ng-container>
</td>
</ng-container>

View File

@ -27,11 +27,11 @@ export class ShowTaskScheduleComponent implements OnInit{
columns = [
{ columnDef: 'id', header: 'ID', cell: (schedule: any) => schedule.id },
{ columnDef: 'recurrenceType', header: 'Recurrencia', cell: (schedule: any) => schedule.recurrenceType },
{ columnDef: 'recurrenceType', header: 'Tipo de Programación', cell: (schedule: any) => this.getRecurrenceTypeDisplay(schedule) },
{ columnDef: 'executionTime', header: 'Hora de ejecución', cell: (schedule: any) => this.datePipe.transform(schedule.executionTime, 'HH:mm') },
{ columnDef: 'nextExecution', header: 'Próxima ejecución', cell: (schedule: any) => this.calculateNextExecution(schedule) },
{ columnDef: 'daysOfWeek', header: 'Días de la semana', cell: (schedule: any) => schedule.recurrenceDetails?.daysOfWeek || [] },
{ columnDef: 'months', header: 'Meses', cell: (schedule: any) => schedule.recurrenceDetails?.months || [] },
{ columnDef: 'daysOfWeek', header: 'Días de la semana', cell: (schedule: any) => this.formatDaysOfWeek(schedule.recurrenceDetails?.daysOfWeek) },
{ columnDef: 'months', header: 'Meses', cell: (schedule: any) => this.formatMonths(schedule.recurrenceDetails?.months) },
{ columnDef: 'enabled', header: 'Estado', cell: (schedule: any) => schedule.enabled }
];
@ -121,23 +121,49 @@ export class ShowTaskScheduleComponent implements OnInit{
try {
if (schedule.recurrenceType === 'none') {
if (schedule.executionDate) {
const execDate = new Date(schedule.executionDate);
if (schedule.executionDate && schedule.executionTime) {
const executionDate = new Date(schedule.executionDate);
const [hours, minutes] = this.parseExecutionTime(schedule.executionTime);
const fullExecutionDateTime = new Date(
executionDate.getFullYear(),
executionDate.getMonth(),
executionDate.getDate(),
hours,
minutes,
0,
0
);
const now = new Date();
if (execDate < now) {
if (fullExecutionDateTime < now) {
return 'Ya ejecutada';
}
return this.datePipe.transform(execDate, 'dd/MM/yyyy HH:mm') || 'Fecha inválida';
return this.datePipe.transform(fullExecutionDateTime, 'dd/MM/yyyy HH:mm') || 'Fecha inválida';
}
return 'Sin fecha';
return 'Sin fecha/hora definida';
}
// Personalizada (u otros tipos con detalles): calcular siguiente ejecución real
if (schedule.recurrenceDetails) {
const days = schedule.recurrenceDetails.daysOfWeek?.join(', ') || 'Todos';
const months = schedule.recurrenceDetails.months?.join(', ') || 'Todos';
return `${days} - ${months}`;
const startDate = schedule.recurrenceDetails.initDate ? new Date(schedule.recurrenceDetails.initDate) : null;
const endDate = schedule.recurrenceDetails.endDate ? new Date(schedule.recurrenceDetails.endDate) : null;
const days = Array.isArray(schedule.recurrenceDetails.daysOfWeek) ? schedule.recurrenceDetails.daysOfWeek : [];
const months = Array.isArray(schedule.recurrenceDetails.months) ? schedule.recurrenceDetails.months : [];
if (!startDate || !endDate || days.length === 0 || months.length === 0) {
return 'Configuración incompleta';
}
const selectedDayIndices = new Set<number>(days.map((d: string) => this.getDayIndexFromName(d)));
const selectedMonthIndices = new Set<number>(months.map((m: string) => this.getMonthIndexFromName(m)));
const [hours, minutes] = this.parseExecutionTime(schedule.executionTime);
const next = this.findNextExecutionDate(startDate, endDate, hours, minutes, selectedDayIndices, selectedMonthIndices);
if (next) {
return this.datePipe.transform(next, 'dd/MM/yyyy HH:mm') || 'Fecha inválida';
}
return 'No hay próximas ejecuciones';
}
return 'Configuración incompleta';
@ -146,6 +172,76 @@ export class ShowTaskScheduleComponent implements OnInit{
}
}
private parseExecutionTime(executionTime: any): [number, number] {
if (typeof executionTime === 'string') {
const match = executionTime.match(/^(\d{2}):(\d{2})/);
if (match) {
return [parseInt(match[1], 10), parseInt(match[2], 10)];
}
const parsed = new Date(executionTime);
if (!isNaN(parsed.getTime())) {
return [parsed.getHours(), parsed.getMinutes()];
}
return [0, 0];
}
if (executionTime instanceof Date && !isNaN(executionTime.getTime())) {
return [executionTime.getHours(), executionTime.getMinutes()];
}
return [0, 0];
}
private findNextExecutionDate(startDate: Date, endDate: Date, hours: number, minutes: number, selectedDays: Set<number>, selectedMonths: Set<number>): Date | null {
const now = new Date();
const startCandidate = new Date(Math.max(new Date(startDate).setHours(0, 0, 0, 0), new Date(now).setHours(0, 0, 0, 0)));
const endLimit = new Date(endDate);
endLimit.setHours(23, 59, 59, 999);
const cursor = new Date(startCandidate);
while (cursor <= endLimit) {
if (selectedMonths.has(cursor.getMonth()) && selectedDays.has(cursor.getDay())) {
const candidateDateTime = new Date(cursor);
candidateDateTime.setHours(hours, minutes, 0, 0);
if (candidateDateTime >= now) {
return candidateDateTime;
}
}
cursor.setDate(cursor.getDate() + 1);
}
return null;
}
private getDayIndexFromName(dayName: string): number {
const mapping: { [key: string]: number } = {
sunday: 0,
monday: 1,
tuesday: 2,
wednesday: 3,
thursday: 4,
friday: 5,
saturday: 6
};
return mapping[dayName?.toLowerCase?.()] ?? -1;
}
private getMonthIndexFromName(monthName: string): number {
const mapping: { [key: string]: number } = {
january: 0,
february: 1,
march: 2,
april: 3,
may: 4,
june: 5,
july: 6,
august: 7,
september: 8,
october: 9,
november: 10,
december: 11
};
return mapping[monthName?.toLowerCase?.()] ?? -1;
}
onPageChange(event: any): void {
this.page = event.pageIndex;
this.itemsPerPage = event.pageSize;
@ -155,4 +251,105 @@ export class ShowTaskScheduleComponent implements OnInit{
onNoClick(): void {
this.dialogRef.close(false);
}
getRecurrenceTypeDisplay(schedule: any): any {
if (schedule.recurrenceType === 'none') {
return {
type: 'none',
label: 'Ejecución única',
icon: 'event',
color: 'primary',
details: schedule.executionDate ?
`Fecha: ${this.datePipe.transform(schedule.executionDate, 'dd/MM/yyyy')}` :
'Sin fecha definida'
};
} else if (schedule.recurrenceType === 'daily') {
return {
type: 'daily',
label: 'Diaria',
icon: 'repeat',
color: 'accent',
details: 'Todos los días'
};
} else if (schedule.recurrenceType === 'weekly') {
return {
type: 'weekly',
label: 'Semanal',
icon: 'view_week',
color: 'accent',
details: schedule.recurrenceDetails?.daysOfWeek?.length > 0 ?
`Días: ${schedule.recurrenceDetails.daysOfWeek.join(', ')}` :
'Todos los días'
};
} else if (schedule.recurrenceType === 'monthly') {
return {
type: 'monthly',
label: 'Mensual',
icon: 'calendar_month',
color: 'accent',
details: schedule.recurrenceDetails?.months?.length > 0 ?
`Meses: ${schedule.recurrenceDetails.months.join(', ')}` :
'Todos los meses'
};
} else if (schedule.recurrenceType === 'yearly') {
return {
type: 'yearly',
label: 'Anual',
icon: 'event_note',
color: 'accent',
details: 'Una vez al año'
};
} else {
return {
type: 'custom',
label: 'Personalizada',
icon: 'settings',
color: 'warn',
details: 'Configuración especial'
};
}
}
formatDaysOfWeek(days: string[]): any {
if (!days || days.length === 0) return null;
const dayMap: { [key: string]: string } = {
'monday': 'Lun',
'tuesday': 'Mar',
'wednesday': 'Mié',
'thursday': 'Jue',
'friday': 'Vie',
'saturday': 'Sáb',
'sunday': 'Dom'
};
return {
days: days.map(day => dayMap[day.toLowerCase()] || day),
count: days.length
};
}
formatMonths(months: string[]): any {
if (!months || months.length === 0) return null;
const monthMap: { [key: string]: string } = {
'january': 'Ene',
'february': 'Feb',
'march': 'Mar',
'april': 'Abr',
'may': 'May',
'june': 'Jun',
'july': 'Jul',
'august': 'Ago',
'september': 'Sep',
'october': 'Oct',
'november': 'Nov',
'december': 'Dic'
};
return {
months: months.map(month => monthMap[month.toLowerCase()] || month),
count: months.length
};
}
}

View File

@ -28,8 +28,8 @@ export class ShowTaskScriptComponent implements OnInit{
{ columnDef: 'id', header: 'ID', cell: (client: any) => client.id },
{ columnDef: 'order', header: 'Orden', cell: (client: any) => client.order },
{ columnDef: 'content', header: 'Script', cell: (client: any) => client.content },
{ columnDef: 'type', header: 'Type', cell: (client: any) => client.type },
{ columnDef: 'parameters', header: 'Parameters', cell: (client: any) => client.parameters },
{ columnDef: 'type', header: 'Tipo', cell: (client: any) => client.type },
{ columnDef: 'parameters', header: 'Parámetros', cell: (client: any) => client.parameters },
];
displayedColumns: string[] = ['id', 'order', 'type', 'parameters', 'content', 'actions'];

View File

@ -124,6 +124,13 @@
</div>
</div>
<!-- Checkbox para forzar push cuando estamos en modo modificar imagen git -->
<div class="force-push-container" *ngIf="imageType === 'git' && hasValidGitData">
<mat-checkbox [(ngModel)]="forcePush" class="force-push-checkbox">
Opción 'Forzar push'
</mat-checkbox>
</div>
<div class="repository-header" *ngIf="!hasValidGitData">
<button *ngIf="imageType === 'git'"
class="create-repository-button"

View File

@ -56,6 +56,7 @@ export class CreateClientImageComponent implements OnInit{
isDestinationBranchEditable: boolean = false;
repositoryNotFound: boolean = false;
newlyCreatedRepository: any = null;
forcePush: boolean = false; // Nueva propiedad para el checkbox de forzar push
get hasValidGitData(): boolean {
return this.gitData && this.gitData.repo && this.gitData.branch;
@ -406,6 +407,7 @@ export class CreateClientImageComponent implements OnInit{
gitRepository: gitRepoName,
originalBranch: originBranch,
destinationBranch: this.destinationBranch,
options: this.forcePush,
queue: result
};
}
@ -641,15 +643,12 @@ export class CreateClientImageComponent implements OnInit{
openScheduleModal(): void {
let scope = 'clients';
// Verificar que tenemos la información del cliente
if (!this.client) {
this.toastService.error('No se ha cargado la información del cliente');
return;
}
// Crear un array con el objeto cliente completo
let selectedClients = [this.client];
console.log(selectedClients);
const dialogRef = this.dialog.open(CreateTaskComponent, {
width: '800px',
@ -657,14 +656,13 @@ export class CreateClientImageComponent implements OnInit{
scope: scope,
selectedClients: selectedClients,
organizationalUnit: this.client['@id'],
source: 'create-image',
source: 'assistant',
runScriptContext: null
}
});
dialogRef.afterClosed().subscribe((result: any) => {
if (result) {
// Verificar que tenemos la partición seleccionada
if (!this.selectedPartition) {
this.toastService.error('Debe seleccionar una partición');
return;
@ -678,7 +676,8 @@ export class CreateClientImageComponent implements OnInit{
action: this.monolithicAction,
diskNumber: this.selectedPartition.diskNumber,
partitionNumber: this.selectedPartition.partitionNumber,
imageName: this.monolithicAction === 'create' ? this.name : this.selectedImage?.name
imageName: this.monolithicAction === 'create' ? this.name : this.selectedImage?.name,
imageUuid: this.monolithicAction === 'update' ? this.selectedImage?.uuid : null
};
} else if (this.imageType === 'git') {
payload = {

View File

@ -48,6 +48,6 @@ export class ConvertImageToVirtualComponent implements OnInit {
}
close() {
this.dialogRef.close(true);
this.dialogRef.close(false);
}
}

View File

@ -50,6 +50,6 @@ export class ConvertImageComponent implements OnInit{
}
close() {
this.dialogRef.close(true);
this.dialogRef.close(false);
}
}

View File

@ -65,6 +65,6 @@ export class EditImageComponent implements OnInit{
}
close() {
this.dialogRef.close();
this.dialogRef.close(false);
}
}

View File

@ -49,6 +49,6 @@ export class ImportImageComponent implements OnInit {
}
close() {
this.dialogRef.close(true);
this.dialogRef.close(false);
}
}

View File

@ -84,8 +84,10 @@ export class RepositoriesComponent implements OnInit {
width: '600px'
});
dialogRef.afterClosed().subscribe(() => {
this.search();
dialogRef.afterClosed().subscribe((result) => {
if (result) {
this.search();
}
});
}
@ -108,7 +110,11 @@ export class RepositoriesComponent implements OnInit {
this.dialog.open(ManageRepositoryComponent, {
width: '600px',
data: repository['@id']
}).afterClosed().subscribe(() => this.search());
}).afterClosed().subscribe((result) => {
if (result) {
this.search();
}
});
}
deleteRepository(event: MouseEvent,command: any): void {
@ -141,7 +147,9 @@ export class RepositoriesComponent implements OnInit {
});
dialogRef.afterClosed().subscribe(result => {
this.search();
if (result) {
this.search();
}
});
}
@ -155,7 +163,9 @@ export class RepositoriesComponent implements OnInit {
});
dialogRef.afterClosed().subscribe(result => {
this.search();
if (result) {
this.search();
}
});
}

View File

@ -7,6 +7,11 @@
box-sizing: border-box;
}
::ng-deep .mat-mdc-dialog-content {
height: 100% !important;
max-height: 100vh !important;
}
.header-container {
display: flex;
align-items: center;

View File

@ -144,7 +144,6 @@ export class ShowMonoliticImagesComponent implements OnInit {
showImageInfo(event: MouseEvent, image:any) {
event.stopPropagation();
this.loading = true;
this.loadImageAlert(image).subscribe(
response => {
this.alertMessage = response;
@ -193,7 +192,6 @@ export class ShowMonoliticImagesComponent implements OnInit {
}
toggleAction(image: any, action:string): void {
this.loading = true;
switch (action) {
case 'get-aux':
this.http.post(`${this.baseUrl}/image-image-repositories/server/${image.uuid}/create-aux-files`, {}).subscribe({