@c8y/api-doc 1023.48.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Binary file
@@ -0,0 +1,10 @@
1
+ import { IApplication } from '@c8y/client';
2
+
3
+ export interface ApiDocApp extends IApplication {
4
+ appLabel: string;
5
+ downloadPath: string;
6
+ manifest: {
7
+ openApiSpec?: string;
8
+ [key: string]: any;
9
+ };
10
+ }
@@ -0,0 +1,108 @@
1
+ <c8y-title>API Documentation</c8y-title>
2
+
3
+ <div class="card">
4
+ <div class="card-block p-32">
5
+ <h1 translate>Welcome to Cumulocity API Documentation</h1>
6
+ <p
7
+ class="m-b-16 text-muted"
8
+ translate
9
+ >
10
+ Your comprehensive, version-aligned API reference for the Cumulocity IoT Platform
11
+ </p>
12
+ <hr class="m-b-16" />
13
+ <p
14
+ class="m-b-24"
15
+ translate
16
+ >
17
+ This application provides complete API documentation extracted directly from your installed
18
+ Cumulocity core and microservices. Every endpoint, parameter, and response is automatically
19
+ synchronized with your current platform version, ensuring accuracy and reliability.
20
+ </p>
21
+ </div>
22
+ </div>
23
+
24
+ <div
25
+ class="card-group"
26
+ aria-label="API documentation features"
27
+ role="list"
28
+ >
29
+ <!-- Card 1: How to Use -->
30
+ <div
31
+ class="col-lg-6"
32
+ role="listitem"
33
+ >
34
+ <div class="card">
35
+ <div class="card-header separator">
36
+ <p
37
+ class="card-title"
38
+ translate
39
+ >
40
+ How to use this app
41
+ </p>
42
+ </div>
43
+ <div class="card-block">
44
+ <p translate>
45
+ This application provides comprehensive API documentation using the industry-standard
46
+ Swagger/OpenAPI format. Each API endpoint is fully documented with:
47
+ </p>
48
+ <ul class="m-t-16 m-b-16">
49
+ <li>
50
+ <strong translate>Complete specifications:</strong>
51
+ <p translate>Parameters, request/response schemas, and examples</p>
52
+ </li>
53
+ <li>
54
+ <strong translate>Try it out:</strong>
55
+ <p translate>Execute API calls directly from your browser using your current session</p>
56
+ </li>
57
+ <li>
58
+ <strong translate>Code samples:</strong>
59
+ <p translate>See real request and response examples for each endpoint</p>
60
+ </li>
61
+ </ul>
62
+ </div>
63
+ </div>
64
+ </div>
65
+
66
+ <!-- Card 2: API List -->
67
+ <div
68
+ class="col-lg-6"
69
+ role="listitem"
70
+ >
71
+ <div class="card">
72
+ <div class="card-header separator">
73
+ <p
74
+ class="card-title"
75
+ translate
76
+ >
77
+ Available APIs
78
+ </p>
79
+ </div>
80
+ <c8y-list-group
81
+ class="c8y-nav-stacked"
82
+ role="list"
83
+ >
84
+ @for (app of documentedApps$ | async; track app.id) {
85
+ <c8y-li
86
+ class="c8y-stacked-item"
87
+ role="listitem"
88
+ [selectable]="false"
89
+ [routerLink]="`/api-docs/${app.id}`"
90
+ >
91
+ <c8y-li-icon>
92
+ <c8y-app-icon
93
+ class="icon-40"
94
+ [app]="app"
95
+ ></c8y-app-icon>
96
+ </c8y-li-icon>
97
+ <c8y-li-body class="icon-flex">
98
+ <strong>{{ app.appLabel }}</strong>
99
+ </c8y-li-body>
100
+ <c8y-li-footer translate>
101
+ {{ app.description || 'No description provided' }}
102
+ </c8y-li-footer>
103
+ </c8y-li>
104
+ }
105
+ </c8y-list-group>
106
+ </div>
107
+ </div>
108
+ </div>
@@ -0,0 +1,36 @@
1
+ import { AsyncPipe } from '@angular/common';
2
+ import { Component, inject } from '@angular/core';
3
+ import { RouterLink } from '@angular/router';
4
+ import {
5
+ AppIconComponent,
6
+ C8yTranslateDirective,
7
+ ListGroupComponent,
8
+ ListItemBodyComponent,
9
+ ListItemComponent,
10
+ ListItemFooterComponent,
11
+ ListItemIconComponent,
12
+ TitleComponent
13
+ } from '@c8y/ngx-components';
14
+ import { ApiDocService } from '../api-doc.service';
15
+
16
+ @Component({
17
+ selector: 'api-doc-home',
18
+ templateUrl: './api-doc-home.component.html',
19
+ standalone: true,
20
+ imports: [
21
+ TitleComponent,
22
+ ListGroupComponent,
23
+ ListItemComponent,
24
+ ListItemIconComponent,
25
+ ListItemBodyComponent,
26
+ ListItemFooterComponent,
27
+ AsyncPipe,
28
+ RouterLink,
29
+ C8yTranslateDirective,
30
+ AppIconComponent
31
+ ]
32
+ })
33
+ export class ApiDocHomeComponent {
34
+ private apiDocService = inject(ApiDocService);
35
+ documentedApps$ = this.apiDocService.getApiDocApps();
36
+ }
@@ -0,0 +1,36 @@
1
+ import { inject, Injectable } from '@angular/core';
2
+ import { ExtensionFactory, NavigatorNode } from '@c8y/ngx-components';
3
+ import { map, Observable } from 'rxjs';
4
+ import { ApiDocApp } from './api-doc-app.model';
5
+ import { ApiDocService } from './api-doc.service';
6
+ import { gettext } from '@c8y/ngx-components/gettext';
7
+ import { createAppIconComponent } from './app-icon/app-icon-navigator.component';
8
+
9
+ @Injectable({
10
+ providedIn: 'root'
11
+ })
12
+ export class ApiDocNavigatorFactory implements ExtensionFactory<NavigatorNode> {
13
+ private apiDocService = inject(ApiDocService);
14
+
15
+ get(): Observable<NavigatorNode> {
16
+ return this.apiDocService.getApiDocApps().pipe(
17
+ map(apps => {
18
+ return new NavigatorNode({
19
+ path: 'api-docs',
20
+ icon: 'api',
21
+ label: gettext('API Documentation'),
22
+ priority: 1000,
23
+ children: apps.map(
24
+ (app: ApiDocApp, index: number) =>
25
+ new NavigatorNode({
26
+ label: app.appLabel,
27
+ iconComponent: createAppIconComponent(app),
28
+ path: `/api-docs/${app.id}`,
29
+ priority: index * -1
30
+ })
31
+ )
32
+ });
33
+ })
34
+ );
35
+ }
36
+ }
@@ -0,0 +1,18 @@
1
+ import { hookNavigator, hookRoute } from '@c8y/ngx-components';
2
+ import { ApiDocNavigatorFactory } from './api-doc-navigator.factory';
3
+
4
+ export const apiDocProviders = [
5
+ hookRoute([
6
+ {
7
+ path: 'api-docs',
8
+ loadComponent: () =>
9
+ import('./api-doc-home/api-doc-home.component').then(m => m.ApiDocHomeComponent)
10
+ },
11
+ {
12
+ path: 'api-docs/:id',
13
+
14
+ loadComponent: () => import('./swagger/swagger.component').then(m => m.SwaggerComponent)
15
+ }
16
+ ]),
17
+ hookNavigator(ApiDocNavigatorFactory)
18
+ ];
@@ -0,0 +1,100 @@
1
+ import { inject, Injectable } from '@angular/core';
2
+ import { ApplicationService } from '@c8y/client';
3
+ import { AppStateService, HumanizeAppNamePipe } from '@c8y/ngx-components';
4
+ import { combineLatest, from, map, Observable, of, shareReplay, switchMap, take } from 'rxjs';
5
+ import { ApiDocApp } from './api-doc-app.model';
6
+ import { sortBy } from 'lodash';
7
+ import { gettext } from '@c8y/ngx-components/gettext';
8
+
9
+ @Injectable({
10
+ providedIn: 'root'
11
+ })
12
+ export class ApiDocService {
13
+ private appService = inject(ApplicationService);
14
+ private appStateService = inject(AppStateService);
15
+ private humanize = inject(HumanizeAppNamePipe);
16
+ private cache$: Observable<ApiDocApp[]> | null = null;
17
+
18
+ private readonly CORE_API_DOCS_APP: ApiDocApp = {
19
+ id: 'core-api',
20
+ name: gettext('Core API'),
21
+ description: gettext('Cumulocity IoT Core API Documentation'),
22
+ contextPath: 'api-doc',
23
+ downloadPath: './c8y-oas.yml',
24
+ icon: {
25
+ class: 'c8y-icon-cumulocity-iot'
26
+ },
27
+ manifest: {
28
+ openApiSpec: 'c8y-oas.yml'
29
+ },
30
+ appLabel: gettext('Core API')
31
+ };
32
+
33
+ /**
34
+ * Get all applications that have API documentation.
35
+ * Results are cached and shared across subscribers.
36
+ */
37
+ getApiDocApps(): Observable<ApiDocApp[]> {
38
+ if (!this.cache$) {
39
+ this.cache$ = combineLatest([
40
+ from(this.appService.list({ pageSize: 2000 })),
41
+ this.appStateService.currentApplication
42
+ ]).pipe(
43
+ map(([apps, currentApp]) => {
44
+ // Update coreApiDocApps with current app's contextPath
45
+ const coreApiDoc: ApiDocApp = {
46
+ ...this.CORE_API_DOCS_APP,
47
+ contextPath: currentApp?.contextPath || 'api-doc'
48
+ };
49
+
50
+ // Filter apps with openApiSpec
51
+ const appsWithOpenApiSpec = apps.data.filter(
52
+ app => !!(app.manifest && app.manifest['openApiSpec'])
53
+ );
54
+ return {
55
+ appsWithOpenApiSpec: sortBy(appsWithOpenApiSpec, app => app.name?.toLowerCase() || ''),
56
+ coreApiDoc
57
+ };
58
+ }),
59
+ switchMap(({ appsWithOpenApiSpec, coreApiDoc }) => {
60
+ if (appsWithOpenApiSpec.length === 0) {
61
+ return of([coreApiDoc] as ApiDocApp[]);
62
+ }
63
+ const appObservables = appsWithOpenApiSpec.map(app =>
64
+ this.humanize.transform(app.name).pipe(
65
+ map(
66
+ humanizedName =>
67
+ ({
68
+ ...app,
69
+ appLabel: humanizedName,
70
+ downloadPath: `/service/${app.contextPath}/${app.manifest['openApiSpec']}`
71
+ }) as ApiDocApp
72
+ )
73
+ )
74
+ );
75
+ return combineLatest(appObservables).pipe(map(apiDocApps => [coreApiDoc, ...apiDocApps]));
76
+ }),
77
+ shareReplay(1)
78
+ );
79
+ }
80
+ return this.cache$;
81
+ }
82
+
83
+ /**
84
+ * Get a specific API doc app by ID.
85
+ * Uses cached data if available, otherwise fetches all apps.
86
+ */
87
+ getApiDocById(id: string | number): Observable<ApiDocApp | undefined> {
88
+ return this.getApiDocApps().pipe(
89
+ take(1),
90
+ map(apps => apps.find(app => app.id === id?.toString()))
91
+ );
92
+ }
93
+
94
+ /**
95
+ * Clear the cache and force a refresh on next call
96
+ */
97
+ clearCache(): void {
98
+ this.cache$ = null;
99
+ }
100
+ }
@@ -0,0 +1,4 @@
1
+ <c8y-app-icon
2
+ class="icon-16 api-app-navigator-icon"
3
+ [app]="app"
4
+ ></c8y-app-icon>
@@ -0,0 +1,13 @@
1
+ .api-app-navigator-icon {
2
+ width: 24px;
3
+ margin-left: 16px;
4
+ display: flex;
5
+ justify-content: center;
6
+
7
+ ::ng-deep .app-noicon {
8
+ width: 22px;
9
+ height: 22px;
10
+ font-size: 14px;
11
+ line-height: 22px;
12
+ }
13
+ }
@@ -0,0 +1,17 @@
1
+ import { Component } from '@angular/core';
2
+ import { AppIconComponent } from '@c8y/ngx-components';
3
+
4
+ export function createAppIconComponent(app: any) {
5
+ @Component({
6
+ selector: 'api-app-icon-navigator',
7
+ templateUrl: './app-icon-navigator.component.html',
8
+ styleUrls: ['./app-icon-navigator.component.less'],
9
+ standalone: true,
10
+ imports: [AppIconComponent]
11
+ })
12
+ class AppIconNavigatorComponent {
13
+ app = app;
14
+ }
15
+
16
+ return AppIconNavigatorComponent;
17
+ }
@@ -0,0 +1,18 @@
1
+ import { ApplicationConfig, importProvidersFrom } from '@angular/core';
2
+ import { provideAnimations } from '@angular/platform-browser/animations';
3
+ import { CoreModule, hookRoute, RouterModule } from '@c8y/ngx-components';
4
+
5
+ export const appConfig: ApplicationConfig = {
6
+ providers: [
7
+ provideAnimations(),
8
+ importProvidersFrom(RouterModule.forRoot()),
9
+ importProvidersFrom(CoreModule.forRoot()),
10
+ hookRoute([
11
+ {
12
+ path: '',
13
+ redirectTo: 'api-docs',
14
+ pathMatch: 'full'
15
+ }
16
+ ])
17
+ ]
18
+ };
@@ -0,0 +1,54 @@
1
+ <c8y-title>
2
+ {{ (spec$ | async)?.app.appLabel | translate }}
3
+ </c8y-title>
4
+
5
+ <c8y-action-bar-item [placement]="'right'">
6
+ <button
7
+ class="btn btn-link"
8
+ title="{{ 'Download' | translate }}"
9
+ type="button"
10
+ (click)="downloadSpecFile()"
11
+ >
12
+ <i
13
+ class="m-r-4"
14
+ [c8yIcon]="'download'"
15
+ ></i>
16
+ <span translate>Download</span>
17
+ </button>
18
+ </c8y-action-bar-item>
19
+ @if (!(shouldHideAlert$ | async)) {
20
+ <div class="d-flex j-c-center m-t-16 m-b-16">
21
+ <div
22
+ class="alert alert-warning"
23
+ role="alert"
24
+ >
25
+ <button
26
+ class="close"
27
+ aria-label="Close alert"
28
+ type="button"
29
+ (click)="hideAlert()"
30
+ >
31
+ <span aria-hidden="true">
32
+ <i [c8yIcon]="'times'"></i>
33
+ </span>
34
+ <span class="sr-only">Close</span>
35
+ </button>
36
+ <strong translate>Authenticated requests (use with caution):</strong>
37
+ <p translate>
38
+ Authentication is provided via the current application session (cookie based). When you copy
39
+ cURL snippets, ensure you include the session cookie or any other authentication method, to
40
+ authenticate your requests.
41
+ </p>
42
+ <p translate>
43
+ The "Try it out" feature executes real API calls against your live system. DELETE, PUT, and
44
+ POST operations will modify or remove data permanently. Always verify your inputs before
45
+ executing destructive operations.
46
+ </p>
47
+ </div>
48
+ </div>
49
+ }
50
+
51
+ <div
52
+ id="api-documentation"
53
+ #apiDocElement
54
+ ></div>
@@ -0,0 +1,274 @@
1
+ @import '~swagger-ui-dist/swagger-ui.css';
2
+
3
+ .swagger-ui .info .title small pre {
4
+ background: transparent !important;
5
+ border: 0;
6
+ }
7
+
8
+ .swagger-ui .opblock-body pre.microlight {
9
+ background-color: var(--c8y-palette-gray-10) !important;
10
+ }
11
+
12
+ body .swagger-ui .info {
13
+ h1,
14
+ h2,
15
+ h3,
16
+ h4,
17
+ h5,
18
+ li,
19
+ p,
20
+ table {
21
+ color: var(--text-color, var(--c8y-text-color));
22
+ }
23
+ }
24
+
25
+ body .swagger-ui {
26
+ font-family: var(--c8y-font-family-base) !important;
27
+
28
+ *:not(pre):not(code):not(.model):not(pre code span):not(.body-param__text):not(.property-row):not(
29
+ .property-row td
30
+ ):not(.model-title__text):not(.headerline) {
31
+ font-family: var(--c8y-font-family-base) !important;
32
+ }
33
+
34
+ .wrapper {
35
+ padding: 0;
36
+ margin: 0;
37
+ }
38
+
39
+ .info {
40
+ margin: 0;
41
+
42
+ .title {
43
+ display: flex;
44
+ align-items: center;
45
+
46
+ small {
47
+ top: 0;
48
+ vertical-align: unset;
49
+
50
+ pre {
51
+ font-family: var(--c8y-font-family-base) !important;
52
+ padding: 0 4px;
53
+ }
54
+ }
55
+ }
56
+ }
57
+
58
+ .servers > label {
59
+ margin-top: 0;
60
+ }
61
+
62
+ .scheme-container {
63
+ padding-top: 0;
64
+ box-shadow: var(--c8y-elevation-sm);
65
+ background: var(--c8y-level-1);
66
+ padding: 16px 16px;
67
+ border: 1px solid var(--c8y-root-component-border-color);
68
+ max-width: 1460px;
69
+ }
70
+
71
+ .opblock-tag-section {
72
+ box-shadow: var(--c8y-elevation-sm);
73
+ margin-bottom: 24px;
74
+ }
75
+
76
+ .operation-tag-content {
77
+ padding: 16px;
78
+ }
79
+
80
+ .opblock-tag {
81
+ margin: 0;
82
+ padding: 16px;
83
+ border: 0;
84
+ box-shadow: inset 0 -1px 0
85
+ var(--c8y-component-border-color, var(--c8y-root-component-border-color));
86
+ display: flex;
87
+ flex-wrap: wrap;
88
+ align-items: flex-start;
89
+
90
+ &:hover {
91
+ color: var(--c8y-brand-primary);
92
+ background-color: transparent;
93
+ }
94
+
95
+ > a {
96
+ flex: 0 0 auto;
97
+ }
98
+
99
+ > small {
100
+ order: 3;
101
+ flex: 1 1 100%;
102
+ font-size: 14px;
103
+ line-height: 1.5;
104
+ margin-top: 12px;
105
+ display: block;
106
+ }
107
+
108
+ > .expand-operation {
109
+ margin-left: auto;
110
+ }
111
+ }
112
+
113
+ .scheme-container .schemes .auth-wrapper .authorize {
114
+ align-items: center;
115
+ color: var(--c8y-btn-default-color-default);
116
+ background-color: var(--c8y-btn-default-background-default);
117
+ border-color: var(--c8y-btn-default-border-color-default);
118
+
119
+ &:hover,
120
+ &:focus {
121
+ color: var(--c8y-btn-default-color-hover);
122
+ background-color: var(--c8y-btn-default-background-hover);
123
+ border-color: var(--c8y-btn-default-border-color-hover);
124
+ box-shadow: inset 0 0 0 1px var(--c8y-btn-default-border-color-hover);
125
+ }
126
+
127
+ svg {
128
+ fill: var(--c8y-btn-default-color-default);
129
+ }
130
+
131
+ span {
132
+ padding: 0;
133
+ }
134
+ }
135
+
136
+ .opblock .opblock-summary-operation-id,
137
+ .opblock .opblock-summary-path,
138
+ .opblock .opblock-summary-path__deprecated,
139
+ .model,
140
+ code,
141
+ .models .model-box {
142
+ &,
143
+ * {
144
+ font-family: monospace !important;
145
+ }
146
+ }
147
+
148
+ .opblock-body pre.microlight {
149
+ font-family: monospace !important;
150
+ font-weight: 400;
151
+
152
+ pre code *,
153
+ pre code span {
154
+ font-family: monospace !important;
155
+ }
156
+ }
157
+
158
+ table {
159
+ background-color: transparent;
160
+ }
161
+
162
+ svg {
163
+ width: unset;
164
+ height: unset;
165
+ }
166
+
167
+ select {
168
+ font-weight: 400;
169
+ border: 1px solid var(--c8y-form-control-border-color-default);
170
+ background-color: var(--c8y-form-control-background-default);
171
+ box-shadow: none;
172
+ border-radius: 0;
173
+
174
+ &:focus {
175
+ border-color: var(--c8y-form-control-border-color-focus);
176
+ box-shadow: inset 0 0 0 2px var(--c8y-form-control-border-color-focus);
177
+ border-radius: 2px;
178
+ }
179
+ }
180
+
181
+ .btn {
182
+ box-shadow: none;
183
+ padding: 5px 16px;
184
+ font-weight: 400;
185
+ border: 1px solid var(--c8y-btn-default-border-color-default);
186
+ background-color: var(--c8y-btn-default-background-default);
187
+ color: var(--c8y-btn-default-color-default);
188
+ border-radius: calc(var(--btn-border-radius-base, var(--c8y-btn-border-radius-base)) * 1px);
189
+
190
+ &:hover,
191
+ &:focus {
192
+ box-shadow: inset 0 0 0 1px var(--c8y-btn-default-border-color-hover);
193
+ }
194
+ }
195
+
196
+ .model-box-control:focus,
197
+ .models-control:focus,
198
+ .opblock-summary-control:focus {
199
+ outline-color: var(--c8y-brand-primary);
200
+ }
201
+
202
+ .markdown code,
203
+ .renderedMarkdown code {
204
+ color: var(--c8y-code-color);
205
+ background: var(--c8y-code-background);
206
+ }
207
+
208
+ .opblock.opblock-get {
209
+ border-color: var(--c8y-palette-status-info-light);
210
+
211
+ .opblock-summary {
212
+ border-color: var(--c8y-palette-status-info-light);
213
+ }
214
+
215
+ .opblock-summary-method {
216
+ background: var(--c8y-palette-status-info);
217
+ }
218
+ }
219
+
220
+ .opblock.opblock-put {
221
+ border-color: var(--c8y-palette-status-warning-light);
222
+
223
+ .opblock-summary {
224
+ border-color: var(--c8y-palette-status-warning-light);
225
+ }
226
+
227
+ .opblock-summary-method {
228
+ background: var(--c8y-palette-status-warning);
229
+ }
230
+ }
231
+
232
+ .opblock.opblock-post {
233
+ border-color: var(--c8y-palette-status-success-light);
234
+
235
+ .opblock-summary {
236
+ border-color: var(--c8y-palette-status-success-light);
237
+ }
238
+
239
+ .opblock-summary-method {
240
+ background: var(--c8y-palette-status-success);
241
+ }
242
+ }
243
+
244
+ .opblock.opblock-delete {
245
+ border-color: var(--c8y-palette-status-danger-light);
246
+
247
+ .opblock-summary {
248
+ border-color: var(--c8y-palette-status-danger-light);
249
+ }
250
+
251
+ .opblock-summary-method {
252
+ background: var(--c8y-palette-status-danger);
253
+ }
254
+ }
255
+
256
+ section.models {
257
+ border-radius: 0;
258
+ border: 0;
259
+ box-shadow: var(--c8y-elevation-sm);
260
+
261
+ > h4 {
262
+ padding: 0;
263
+ font-size: 24px;
264
+ border: 0 !important;
265
+ margin: 0 !important;
266
+ box-shadow: inset 0 -1px 0
267
+ var(--c8y-component-border-color, var(--c8y-root-component-border-color));
268
+
269
+ .models-control {
270
+ padding: 16px;
271
+ }
272
+ }
273
+ }
274
+ }