Skip to content

Commit 4ead5e4

Browse files
committed
* Translate new English document to German
1 parent dcebb59 commit 4ead5e4

File tree

4 files changed

+427
-46
lines changed

4 files changed

+427
-46
lines changed

de/docs/development/create-new-module/additionalplugin-database.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
# Additionalplugin_database
22

33
## Core Tabellen mit Plugin tabellen verlinken
4-
In manchen Fällen wird ein link zu einem openITCOCKPIT core Tabellenobjekt von einem Tabellenobjekt eines Moduls
5-
benötigt.
4+
In manchen Fällen wird ein link zu einem openITCOCKPIT core Tabellenobjekt von einem Tabellenobjekt eines Moduls benötigt.
65

76
In diesem Beispiel möchten wir die "additional notes" aus der Modul Tabelle löschen, sobald der zugehörige Host gelöscht
87
wird. Um dies zu Implementieren müssen wir keinerlei core Code anpassen.
Lines changed: 376 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,376 @@
1+
# Angular Front-End
2+
Das openITCOCKPIT backend-Modul ist also fertig gebacken. Sicher wollen wir das jetzt auch im Front-End benutzen. Dafür brauchen wir ein Front-End Modul.
3+
4+
Dieses Dokument behandelt die folgenden Schritte:
5+
6+
- Ein neues Angular Modul anlegen
7+
- Kommunizieren mit der openITCOCKPIT API
8+
- Generieren der Front-End Komponente
9+
10+
Dieses Beispiel behandelt den Code unseres [openITCOCKPIT ExampleModule Frontend Angular Repository](https://github.com/openITCOCKPIT/openITCOCKPIT-ExampleModule-Frontend-Angular). Hier kann jederzeit das komplette Modul heruntergeladen werden.
11+
12+
## Ein neues Angular Modul anlegen
13+
Zuerst muss der Unterordner für das neue Modul im Angular Projekt angelegt werden. Hier werden dann die Seiten, Controller, Interfaces und Services hinterlegt.
14+
```bash
15+
cd /opt/openitc/frontend-angular/src/app/modules/example_module
16+
mkdir example_module
17+
mkdir example_module/pages
18+
```
19+
20+
### Generating the component
21+
Nachdem wir die Interfaces und den Service definiert haben, wird es Zeit, die Seite als Komponente zu erstellen.
22+
23+
Seiten werden mit folgendem Command als Komponenten erstellt `ng generate`.
24+
```bash
25+
cd /opt/openitc/frontend-angular/src/app/modules/example_module/pages/
26+
ng generate component TestIndex
27+
```
28+
29+
```html
30+
<!-- Hint from a fellow developer:
31+
- Notice, that we use the transloco directive to translate texts on the template.
32+
- This is our preferred way to translate texts in templates.
33+
-->
34+
<ng-container *transloco="let t">
35+
<nav aria-label="breadcrumb" class="mt-3">
36+
<ol class="breadcrumb">
37+
<li class="breadcrumb-item">
38+
<a [routerLink]="['/']">
39+
<fa-icon [icon]="['fas', 'home']"></fa-icon>
40+
<!-- Hint from a fellow developer:
41+
- See that we use the t('string') function to translate the texts.
42+
-->
43+
{{ t('Home') }}
44+
</a></li>
45+
<li class="breadcrumb-item">
46+
<fa-icon [icon]="['fas', 'burn']"></fa-icon>
47+
{{ t('Example Module') }}
48+
</li>
49+
<li class="breadcrumb-item">
50+
<!-- Hint from a fellow developer:
51+
- We add our own directive *oitcPermission here.
52+
- If the user has the permission, the the object is an actual link. If not, it's just a text.
53+
-->
54+
<a [routerLink]="['/', 'example_module', 'test', 'index']"
55+
*oitcPermission="['examplemodule', 'test', 'index']">
56+
<fa-icon [icon]="['fas', 'cogs']"></fa-icon>
57+
{{ t('Example Module') }}
58+
</a>
59+
</li>
60+
<li aria-current="page" class="breadcrumb-item active">
61+
<fa-icon [icon]="['fas', 'edit']"></fa-icon>
62+
{{ t('Hello World') }}
63+
</li>
64+
</ol>
65+
</nav>
66+
67+
<!-- Hint from a fellow developer:
68+
- Note that this component is only shown as long as the response is not yet received.
69+
- This prevents flickering of the form as soon as data is received.
70+
- Note that we use [isVisible] here, which triggers fetching the dynamic page title on change.
71+
-->
72+
<oitc-form-loader [isVisible]="!post"></oitc-form-loader>
73+
74+
<!-- Hint from a fellow developer:
75+
- This is the opposite of the above.
76+
- The form initially shows up as soon as our service received the data.
77+
-->
78+
<form cForm (ngSubmit)="submit()" *ngIf="post">
79+
<c-card class="mb-3">
80+
<c-card-header>
81+
<h5 cCardTitle>
82+
{{ t('Example Module') }}
83+
<small class="fw-300">
84+
{{ t('Hello World') }}
85+
</small>
86+
</h5>
87+
</c-card-header>
88+
<c-card-body>
89+
90+
<div class="row">
91+
<div class="col-12">
92+
93+
<div class="mb-3">
94+
<label cLabel for="post.webhook_url">
95+
{{ t('Webhook URL') }}
96+
<oitc-required-icon></oitc-required-icon>
97+
</label>
98+
99+
<!-- Hint from a fellow developer:
100+
- Check that the input uses the cFormControl directive to make it look similar to the other inputs in openITCOCKPIT.
101+
- The oitcFormError directive with parameters [errors] and errorField activate error handling on the field.
102+
-->
103+
<input cFormControl required
104+
[(ngModel)]="post.webhook_url"
105+
id="post.webhook_url"
106+
name="post.webhook_url"
107+
placeholder="https://mysite.office365.com/myteam-id/channel/mychannel-id/"
108+
type="text"
109+
oitcFormError [errors]="errors" errorField="webhook_url">
110+
111+
<!-- Hint from a fellow developer:
112+
- This element will not be shown if there are no errors.
113+
-->
114+
<oitc-form-feedback [errors]="errors" errorField="webhook_url"></oitc-form-feedback>
115+
</div>
116+
</div>
117+
</div>
118+
</c-card-body>
119+
<c-card-footer class="text-end">
120+
<button cButton class="ripple" color="primary" type="submit">
121+
{{ t('Save configuration') }}
122+
</button>
123+
</c-card-footer>
124+
</c-card>
125+
</form>
126+
</ng-container>
127+
```
128+
129+
Neben dem HTML-Template der neu generierten Komponente, liegt die TypeScript-Datei `test-index.component.ts`:
130+
```typescript
131+
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, inject, OnDestroy, OnInit } from '@angular/core';
132+
import { Subscription } from 'rxjs';
133+
import { NotyService } from '../../../../../layouts/coreui/noty.service';
134+
import { TestService } from '../../../test.service';
135+
import { GenericValidationError } from '../../../../../generic-responses';
136+
import { TestSettings } from '../../../test.interface';
137+
import {
138+
CardBodyComponent,
139+
CardComponent,
140+
CardFooterComponent,
141+
CardHeaderComponent,
142+
CardTitleDirective,
143+
FormControlDirective,
144+
FormDirective,
145+
FormLabelDirective
146+
} from '@coreui/angular';
147+
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
148+
import { FormErrorDirective } from '../../../../../layouts/coreui/form-error.directive';
149+
import { FormFeedbackComponent } from '../../../../../layouts/coreui/form-feedback/form-feedback.component';
150+
import { FormLoaderComponent } from '../../../../../layouts/primeng/loading/form-loader/form-loader.component';
151+
import { FormsModule } from '@angular/forms';
152+
import { NgIf } from '@angular/common';
153+
import { PermissionDirective } from '../../../../../permissions/permission.directive';
154+
import { RequiredIconComponent } from '../../../../../components/required-icon/required-icon.component';
155+
import { TranslocoDirective } from '@jsverse/transloco';
156+
import { XsButtonDirective } from '../../../../../layouts/coreui/xsbutton-directive/xsbutton.directive';
157+
import { RouterLink } from '@angular/router';
158+
159+
@Component({
160+
selector: 'oitc-test-index',
161+
imports: [
162+
CardBodyComponent,
163+
CardComponent,
164+
CardFooterComponent,
165+
CardHeaderComponent,
166+
CardTitleDirective,
167+
FaIconComponent,
168+
FormControlDirective,
169+
FormDirective,
170+
FormErrorDirective,
171+
FormFeedbackComponent,
172+
FormLabelDirective,
173+
FormLoaderComponent,
174+
FormsModule,
175+
NgIf,
176+
PermissionDirective,
177+
RequiredIconComponent,
178+
TranslocoDirective,
179+
XsButtonDirective,
180+
RouterLink
181+
],
182+
templateUrl: './test-index.component.html',
183+
styleUrl: './test-index.component.css',
184+
/* Hint from a fellow developer:
185+
* - Note that we are using the ChangeDetectionStrategy.OnPush strategy.
186+
* - This means that the component will only be checked for changes when told to do so using markForCheck().
187+
* - This is a performance optimization technique in Angular that has effect on the browser.
188+
*/
189+
changeDetection: ChangeDetectionStrategy.OnPush
190+
})
191+
export class TestIndexComponent implements OnInit, OnDestroy {
192+
private readonly Subscriptions: Subscription = new Subscription();
193+
private readonly TestService: TestService = inject(TestService);
194+
private readonly NotyService: NotyService = inject(NotyService);
195+
private readonly cdr: ChangeDetectorRef = inject(ChangeDetectorRef);
196+
197+
/* Hint from a fellow developer:
198+
* - Note that post may be undefined.
199+
* - ngOnInit will fetch the settings from the API and put it here.
200+
*/
201+
protected post?: TestSettings;
202+
203+
/* Hint from a fellow developer:
204+
* - Note that errors may be undefined.
205+
*/
206+
protected errors?: GenericValidationError;
207+
208+
/* Hint from a fellow developer:
209+
* - Note that the Component implements both OnInit and OnDestroy interfaces.
210+
* - This is a common pattern in Angular components to manage subscriptions and lifecycle hooks.
211+
*
212+
* - We use them to fetch data when the component is initialized and clean up resources when the component is destroyed.
213+
*/
214+
public ngOnInit(): void {
215+
this.Subscriptions.add(
216+
this.TestService.getSettings().subscribe((data: TestSettings) => {
217+
this.post = data;
218+
219+
/* Hint from a fellow developer:
220+
* - Note that we are triggering the ChangeDetection manually update the view.
221+
* - Otherwise the user would not see the changes to the form.
222+
*/
223+
this.cdr.markForCheck();
224+
})
225+
);
226+
}
227+
228+
/* Hint from a fellow developer:
229+
* - This cleans up the subscriptions when the component is destroyed.
230+
* - Otherwise the subscriptions would remain active and could lead to memory leaks.
231+
*/
232+
public ngOnDestroy(): void {
233+
this.Subscriptions.unsubscribe();
234+
}
235+
236+
/* Hint from a fellow developer:
237+
* - This method is called when the form is submitted.
238+
* - It checks if the post object is defined and then calls the TestService to submit the settings.
239+
* - If the submission is successful, it shows a success notification.
240+
* - If there are validation errors, it sets the errors property.
241+
*
242+
* - Error display in the form is handled by the oitcFormError directive.
243+
*/
244+
protected submit(): void {
245+
if (!this.post) {
246+
return;
247+
}
248+
249+
this.Subscriptions.add(
250+
this.TestService.postSettings(this.post).subscribe((response) => {
251+
this.errors = undefined;
252+
if (response.success) {
253+
this.NotyService.genericSuccess();
254+
} else {
255+
this.errors = response.data as GenericValidationError;
256+
this.NotyService.genericError();
257+
}
258+
259+
/* Hint from a fellow developer:
260+
* - Note that we are triggering the ChangeDetection manually update the view.
261+
* - Otherwise the user would not see the changes to the form and validation.
262+
*/
263+
this.cdr.markForCheck();
264+
})
265+
);
266+
}
267+
}
268+
```
269+
270+
271+
Die Seite ist nun fertig. Zeit, openITCOCKPIT mitzuteilen, dass wir eine neue Komponente haben. Dazu legen wir einen Router für unser neues Modul an:
272+
```typescript
273+
import { Routes } from '@angular/router';
274+
275+
export const exampleModuleRoutes: Routes = [
276+
{
277+
path: 'example_module/test/index',
278+
loadComponent: () => import('./pages/test/test-index/test-index.component').then(m => m.TestIndexComponent)
279+
}
280+
];
281+
```
282+
283+
Dieser neue Router muss nun noch im Application Router von openITCOCKPIT erwähnt werden, damit er letztendlich gefunden wird.
284+
```typescript
285+
// ...
286+
import { exampleModuleRoutes } from './modules/example_module/example_module.routes';
287+
288+
// ...
289+
const moduleRoutes: Routes = [
290+
// other module routes
291+
...exampleModuleRoutes,
292+
];
293+
```
294+
295+
### Kommunizieren mit der openITCOCKPIT API
296+
Weil wir die API von openITCOCKPIT nutzen wollen, um Daten zu empfangen und zu senden, brauchen wir jetzt sowohl einen Service, als auch ein entsprechendes Interface.
297+
Das Interface definiert die Objektstrukturen, die vom Service benutzt werden, um die API anzusprechen.
298+
299+
Zuerst generieren wir mit dem `ng generate` Kommando einen Service an:
300+
```bash
301+
cd /opt/openitc/frontend-angular/src/app/modules/example_module
302+
ng generate service test
303+
```
304+
305+
Der gerade generierte Service liegt nun hier `/opt/openitc/frontend-angular/src/app/modules/example_module/test.service.ts`.
306+
Der fertige Service sieht wiefolgt aus:
307+
308+
```typescript
309+
import { inject, Injectable } from '@angular/core';
310+
import { HttpClient } from '@angular/common/http';
311+
import { PROXY_PATH } from '../../tokens/proxy-path.token';
312+
import { catchError, map, Observable, of } from 'rxjs';
313+
import { GenericResponseWrapper, GenericSuccessResponse, GenericValidationError } from '../../generic-responses';
314+
import { TestGet, TestSettings } from './test.interface';
315+
316+
@Injectable({
317+
providedIn: 'root'
318+
})
319+
export class TestService {
320+
321+
private readonly http: HttpClient = inject(HttpClient);
322+
private readonly proxyPath: string = inject(PROXY_PATH);
323+
324+
public getSettings(): Observable<TestSettings> {
325+
const proxyPath: string = this.proxyPath;
326+
return this.http.get<TestGet>(`${proxyPath}/example_module/test/index.json`, {
327+
params: {
328+
angular: true
329+
}
330+
}).pipe(
331+
map((data: TestGet) => {
332+
return data.settings
333+
})
334+
)
335+
}
336+
337+
public postSettings(data: TestSettings): Observable<GenericResponseWrapper> {
338+
const proxyPath: string = this.proxyPath;
339+
return this.http.post<GenericResponseWrapper>(`${proxyPath}/example_module/test/index.json?angular=true`, data).pipe(
340+
map(data => {
341+
// Return true on 200 Ok
342+
return {
343+
success: true,
344+
data: {success: true} as GenericSuccessResponse
345+
};
346+
}),
347+
catchError((error: any) => {
348+
const err = error.error.error as GenericValidationError;
349+
return of({
350+
success: false,
351+
data: err
352+
});
353+
})
354+
);
355+
}
356+
}
357+
```
358+
359+
Nachdem wir einen leeren Service angelegt haben, können wir auch die entsprechenden Interfaces in folgender Datei definieren `/opt/openitc/frontend-angular/src/app/modules/example_module/test.interface.ts`:
360+
```typescript
361+
export interface TestGet {
362+
settings: TestSettings
363+
// _csrfToken: any This is important, but it is not used from our service.
364+
}
365+
366+
export interface TestPost {
367+
settings: TestSettings
368+
}
369+
370+
export interface TestSettings {
371+
id: number
372+
webhook_url: string
373+
created: string
374+
modified: string
375+
}
376+
```

0 commit comments

Comments
 (0)