UI: Implement entitiy versions table
This commit is contained in:
parent
a5ff23a0a4
commit
ca3c95afca
@ -294,11 +294,14 @@ public class EntitiesVersionControlController extends BaseController {
|
|||||||
|
|
||||||
String defaultBranch = versionControlService.getVersionControlSettings(tenantId).getDefaultBranch();
|
String defaultBranch = versionControlService.getVersionControlSettings(tenantId).getDefaultBranch();
|
||||||
if (StringUtils.isNotEmpty(defaultBranch)) {
|
if (StringUtils.isNotEmpty(defaultBranch)) {
|
||||||
remoteBranches.remove(defaultBranch);
|
|
||||||
infos.add(new BranchInfo(defaultBranch, true));
|
infos.add(new BranchInfo(defaultBranch, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
remoteBranches.forEach(branch -> infos.add(new BranchInfo(branch, false)));
|
remoteBranches.forEach(branch -> {
|
||||||
|
if (!branch.equals(defaultBranch)) {
|
||||||
|
infos.add(new BranchInfo(branch, false));
|
||||||
|
}
|
||||||
|
});
|
||||||
return infos;
|
return infos;
|
||||||
}, MoreExecutors.directExecutor()));
|
}, MoreExecutors.directExecutor()));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|||||||
@ -411,7 +411,7 @@ public class DefaultGitVersionControlQueueService implements GitVersionControlQu
|
|||||||
}
|
}
|
||||||
if (vcSettings != null) {
|
if (vcSettings != null) {
|
||||||
builder.setVcSettings(ByteString.copyFrom(encodingService.encode(vcSettings)));
|
builder.setVcSettings(ByteString.copyFrom(encodingService.encode(vcSettings)));
|
||||||
} else {
|
} else if (request.requiresSettings()) {
|
||||||
throw new RuntimeException("No entity version control settings provisioned!");
|
throw new RuntimeException("No entity version control settings provisioned!");
|
||||||
}
|
}
|
||||||
return builder;
|
return builder;
|
||||||
|
|||||||
@ -170,7 +170,7 @@ public class GitRepository {
|
|||||||
public PageData<Commit> listCommits(String branch, String path, PageLink pageLink) throws IOException, GitAPIException {
|
public PageData<Commit> listCommits(String branch, String path, PageLink pageLink) throws IOException, GitAPIException {
|
||||||
ObjectId branchId = resolve("origin/" + branch);
|
ObjectId branchId = resolve("origin/" + branch);
|
||||||
if (branchId == null) {
|
if (branchId == null) {
|
||||||
throw new IllegalArgumentException("Branch not found");
|
return new PageData<>();
|
||||||
}
|
}
|
||||||
LogCommand command = git.log()
|
LogCommand command = git.log()
|
||||||
.add(branchId)
|
.add(branchId)
|
||||||
@ -313,6 +313,7 @@ public class GitRepository {
|
|||||||
Function<? super T, ? extends R> mapper,
|
Function<? super T, ? extends R> mapper,
|
||||||
PageLink pageLink,
|
PageLink pageLink,
|
||||||
Function<PageLink, Comparator<T>> comparatorFunction) {
|
Function<PageLink, Comparator<T>> comparatorFunction) {
|
||||||
|
iterable = Streams.stream(iterable).collect(Collectors.toList());
|
||||||
int totalElements = Iterables.size(iterable);
|
int totalElements = Iterables.size(iterable);
|
||||||
int totalPages = pageLink.getPageSize() > 0 ? (int) Math.ceil((float) totalElements / pageLink.getPageSize()) : 1;
|
int totalPages = pageLink.getPageSize() > 0 ? (int) Math.ceil((float) totalElements / pageLink.getPageSize()) : 1;
|
||||||
int startIndex = pageLink.getPageSize() * pageLink.getPage();
|
int startIndex = pageLink.getPageSize() * pageLink.getPage();
|
||||||
|
|||||||
@ -23,7 +23,8 @@ export enum AuthActionTypes {
|
|||||||
UNAUTHENTICATED = '[Auth] Unauthenticated',
|
UNAUTHENTICATED = '[Auth] Unauthenticated',
|
||||||
LOAD_USER = '[Auth] Load User',
|
LOAD_USER = '[Auth] Load User',
|
||||||
UPDATE_USER_DETAILS = '[Auth] Update User Details',
|
UPDATE_USER_DETAILS = '[Auth] Update User Details',
|
||||||
UPDATE_LAST_PUBLIC_DASHBOARD_ID = '[Auth] Update Last Public Dashboard Id'
|
UPDATE_LAST_PUBLIC_DASHBOARD_ID = '[Auth] Update Last Public Dashboard Id',
|
||||||
|
UPDATE_HAS_VERSION_CONTROL = '[Auth] Change Has Version Control'
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ActionAuthAuthenticated implements Action {
|
export class ActionAuthAuthenticated implements Action {
|
||||||
@ -54,5 +55,11 @@ export class ActionAuthUpdateLastPublicDashboardId implements Action {
|
|||||||
constructor(readonly payload: { lastPublicDashboardId: string }) {}
|
constructor(readonly payload: { lastPublicDashboardId: string }) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class ActionAuthUpdateHasVersionControl implements Action {
|
||||||
|
readonly type = AuthActionTypes.UPDATE_HAS_VERSION_CONTROL;
|
||||||
|
|
||||||
|
constructor(readonly payload: { hasVersionControl: boolean }) {}
|
||||||
|
}
|
||||||
|
|
||||||
export type AuthActions = ActionAuthAuthenticated | ActionAuthUnauthenticated |
|
export type AuthActions = ActionAuthAuthenticated | ActionAuthUnauthenticated |
|
||||||
ActionAuthLoadUser | ActionAuthUpdateUserDetails | ActionAuthUpdateLastPublicDashboardId;
|
ActionAuthLoadUser | ActionAuthUpdateUserDetails | ActionAuthUpdateLastPublicDashboardId | ActionAuthUpdateHasVersionControl;
|
||||||
|
|||||||
@ -20,6 +20,7 @@ export interface SysParamsState {
|
|||||||
userTokenAccessEnabled: boolean;
|
userTokenAccessEnabled: boolean;
|
||||||
allowedDashboardIds: string[];
|
allowedDashboardIds: string[];
|
||||||
edgesSupportEnabled: boolean;
|
edgesSupportEnabled: boolean;
|
||||||
|
hasVersionControl: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AuthPayload extends SysParamsState {
|
export interface AuthPayload extends SysParamsState {
|
||||||
|
|||||||
@ -23,7 +23,8 @@ const emptyUserAuthState: AuthPayload = {
|
|||||||
userTokenAccessEnabled: false,
|
userTokenAccessEnabled: false,
|
||||||
forceFullscreen: false,
|
forceFullscreen: false,
|
||||||
allowedDashboardIds: [],
|
allowedDashboardIds: [],
|
||||||
edgesSupportEnabled: false
|
edgesSupportEnabled: false,
|
||||||
|
hasVersionControl: false
|
||||||
};
|
};
|
||||||
|
|
||||||
export const initialState: AuthState = {
|
export const initialState: AuthState = {
|
||||||
@ -54,6 +55,9 @@ export function authReducer(
|
|||||||
case AuthActionTypes.UPDATE_LAST_PUBLIC_DASHBOARD_ID:
|
case AuthActionTypes.UPDATE_LAST_PUBLIC_DASHBOARD_ID:
|
||||||
return { ...state, ...action.payload};
|
return { ...state, ...action.payload};
|
||||||
|
|
||||||
|
case AuthActionTypes.UPDATE_HAS_VERSION_CONTROL:
|
||||||
|
return { ...state, ...action.payload};
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -55,6 +55,11 @@ export const selectUserTokenAccessEnabled = createSelector(
|
|||||||
(state: AuthState) => state.userTokenAccessEnabled
|
(state: AuthState) => state.userTokenAccessEnabled
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const selectHasVersionControl = createSelector(
|
||||||
|
selectAuthState,
|
||||||
|
(state: AuthState) => state.hasVersionControl
|
||||||
|
);
|
||||||
|
|
||||||
export function getCurrentAuthState(store: Store<AppState>): AuthState {
|
export function getCurrentAuthState(store: Store<AppState>): AuthState {
|
||||||
let state: AuthState;
|
let state: AuthState;
|
||||||
store.pipe(select(selectAuth), take(1)).subscribe(
|
store.pipe(select(selectAuth), take(1)).subscribe(
|
||||||
|
|||||||
@ -437,17 +437,27 @@ export class AuthService {
|
|||||||
return this.http.get<boolean>('/api/edges/enabled', defaultHttpOptions());
|
return this.http.get<boolean>('/api/edges/enabled', defaultHttpOptions());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private loadHasVersionControl(authUser: AuthUser): Observable<boolean> {
|
||||||
|
if (authUser.authority === Authority.TENANT_ADMIN) {
|
||||||
|
return this.http.get<boolean>('/api/admin/vcSettings/exists', defaultHttpOptions());
|
||||||
|
} else {
|
||||||
|
return of(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private loadSystemParams(authPayload: AuthPayload): Observable<SysParamsState> {
|
private loadSystemParams(authPayload: AuthPayload): Observable<SysParamsState> {
|
||||||
const sources = [this.loadIsUserTokenAccessEnabled(authPayload.authUser),
|
const sources = [this.loadIsUserTokenAccessEnabled(authPayload.authUser),
|
||||||
this.fetchAllowedDashboardIds(authPayload),
|
this.fetchAllowedDashboardIds(authPayload),
|
||||||
this.loadIsEdgesSupportEnabled(),
|
this.loadIsEdgesSupportEnabled(),
|
||||||
|
this.loadHasVersionControl(authPayload.authUser),
|
||||||
this.timeService.loadMaxDatapointsLimit()];
|
this.timeService.loadMaxDatapointsLimit()];
|
||||||
return forkJoin(sources)
|
return forkJoin(sources)
|
||||||
.pipe(map((data) => {
|
.pipe(map((data) => {
|
||||||
const userTokenAccessEnabled: boolean = data[0] as boolean;
|
const userTokenAccessEnabled: boolean = data[0] as boolean;
|
||||||
const allowedDashboardIds: string[] = data[1] as string[];
|
const allowedDashboardIds: string[] = data[1] as string[];
|
||||||
const edgesSupportEnabled: boolean = data[2] as boolean;
|
const edgesSupportEnabled: boolean = data[2] as boolean;
|
||||||
return {userTokenAccessEnabled, allowedDashboardIds, edgesSupportEnabled};
|
const hasVersionControl: boolean = data[3] as boolean;
|
||||||
|
return {userTokenAccessEnabled, allowedDashboardIds, edgesSupportEnabled, hasVersionControl};
|
||||||
}, catchError((err) => {
|
}, catchError((err) => {
|
||||||
return of({});
|
return of({});
|
||||||
})));
|
})));
|
||||||
|
|||||||
@ -18,7 +18,12 @@ import { Injectable } from '@angular/core';
|
|||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { defaultHttpOptionsFromConfig, RequestConfig } from '@core/http/http-utils';
|
import { defaultHttpOptionsFromConfig, RequestConfig } from '@core/http/http-utils';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { BranchInfo, VersionCreateRequest, VersionCreationResult } from '@shared/models/vc.models';
|
import { BranchInfo, EntityVersion, VersionCreateRequest, VersionCreationResult } from '@shared/models/vc.models';
|
||||||
|
import { PageLink } from '@shared/models/page/page-link';
|
||||||
|
import { PageData } from '@shared/models/page/page-data';
|
||||||
|
import { DeviceInfo } from '@shared/models/device.models';
|
||||||
|
import { EntityId } from '@shared/models/id/entity-id';
|
||||||
|
import { EntityType } from '@shared/models/entity-type.models';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
@ -37,4 +42,24 @@ export class EntitiesVersionControlService {
|
|||||||
public saveEntitiesVersion(request: VersionCreateRequest, config?: RequestConfig): Observable<VersionCreationResult> {
|
public saveEntitiesVersion(request: VersionCreateRequest, config?: RequestConfig): Observable<VersionCreationResult> {
|
||||||
return this.http.post<VersionCreationResult>('/api/entities/vc/version', request, defaultHttpOptionsFromConfig(config));
|
return this.http.post<VersionCreationResult>('/api/entities/vc/version', request, defaultHttpOptionsFromConfig(config));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public listEntityVersions(pageLink: PageLink, branch: string,
|
||||||
|
externalEntityId: EntityId,
|
||||||
|
config?: RequestConfig): Observable<PageData<EntityVersion>> {
|
||||||
|
return this.http.get<PageData<EntityVersion>>(`/api/entities/vc/version/${branch}/${externalEntityId.entityType}/${externalEntityId.id}${pageLink.toQuery()}`,
|
||||||
|
defaultHttpOptionsFromConfig(config));
|
||||||
|
}
|
||||||
|
|
||||||
|
public listEntityTypeVersions(pageLink: PageLink, branch: string,
|
||||||
|
entityType: EntityType,
|
||||||
|
config?: RequestConfig): Observable<PageData<EntityVersion>> {
|
||||||
|
return this.http.get<PageData<EntityVersion>>(`/api/entities/vc/version/${branch}/${entityType}${pageLink.toQuery()}`,
|
||||||
|
defaultHttpOptionsFromConfig(config));
|
||||||
|
}
|
||||||
|
|
||||||
|
public listVersions(pageLink: PageLink, branch: string,
|
||||||
|
config?: RequestConfig): Observable<PageData<EntityVersion>> {
|
||||||
|
return this.http.get<PageData<EntityVersion>>(`/api/entities/vc/version/${branch}${pageLink.toQuery()}`,
|
||||||
|
defaultHttpOptionsFromConfig(config));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -343,6 +343,13 @@ export class MenuService {
|
|||||||
path: '/dashboards',
|
path: '/dashboards',
|
||||||
icon: 'dashboards'
|
icon: 'dashboards'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: guid(),
|
||||||
|
name: 'version-control.version-control',
|
||||||
|
type: 'link',
|
||||||
|
path: '/vc',
|
||||||
|
icon: 'history'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: guid(),
|
id: guid(),
|
||||||
name: 'audit-log.audit-logs',
|
name: 'audit-log.audit-logs',
|
||||||
@ -492,6 +499,16 @@ export class MenuService {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'version-control.management',
|
||||||
|
places: [
|
||||||
|
{
|
||||||
|
name: 'version-control.version-control',
|
||||||
|
icon: 'history',
|
||||||
|
path: '/vc'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'audit-log.audit',
|
name: 'audit-log.audit',
|
||||||
places: [
|
places: [
|
||||||
|
|||||||
@ -154,6 +154,9 @@ import { QueueFormComponent } from '@home/components/queue/queue-form.component'
|
|||||||
import { WidgetSettingsModule } from '@home/components/widget/lib/settings/widget-settings.module';
|
import { WidgetSettingsModule } from '@home/components/widget/lib/settings/widget-settings.module';
|
||||||
import { WidgetSettingsComponent } from '@home/components/widget/widget-settings.component';
|
import { WidgetSettingsComponent } from '@home/components/widget/widget-settings.component';
|
||||||
import { VcEntityExportDialogComponent } from '@home/components/vc/vc-entity-export-dialog.component';
|
import { VcEntityExportDialogComponent } from '@home/components/vc/vc-entity-export-dialog.component';
|
||||||
|
import { VersionControlSettingsComponent } from '@home/components/vc/version-control-settings.component';
|
||||||
|
import { VersionControlComponent } from '@home/components/vc/version-control.component';
|
||||||
|
import { EntityVersionsTableComponent } from '@home/components/vc/entity-versions-table.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations:
|
declarations:
|
||||||
@ -278,7 +281,10 @@ import { VcEntityExportDialogComponent } from '@home/components/vc/vc-entity-exp
|
|||||||
DisplayWidgetTypesPanelComponent,
|
DisplayWidgetTypesPanelComponent,
|
||||||
TenantProfileQueuesComponent,
|
TenantProfileQueuesComponent,
|
||||||
QueueFormComponent,
|
QueueFormComponent,
|
||||||
VcEntityExportDialogComponent
|
VcEntityExportDialogComponent,
|
||||||
|
VersionControlSettingsComponent,
|
||||||
|
VersionControlComponent,
|
||||||
|
EntityVersionsTableComponent
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
@ -397,7 +403,10 @@ import { VcEntityExportDialogComponent } from '@home/components/vc/vc-entity-exp
|
|||||||
DisplayWidgetTypesPanelComponent,
|
DisplayWidgetTypesPanelComponent,
|
||||||
TenantProfileQueuesComponent,
|
TenantProfileQueuesComponent,
|
||||||
QueueFormComponent,
|
QueueFormComponent,
|
||||||
VcEntityExportDialogComponent
|
VcEntityExportDialogComponent,
|
||||||
|
VersionControlSettingsComponent,
|
||||||
|
VersionControlComponent,
|
||||||
|
EntityVersionsTableComponent
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
WidgetComponentService,
|
WidgetComponentService,
|
||||||
|
|||||||
@ -0,0 +1,81 @@
|
|||||||
|
<!--
|
||||||
|
|
||||||
|
Copyright © 2016-2022 The Thingsboard Authors
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|
||||||
|
-->
|
||||||
|
<div class="mat-padding tb-entity-table tb-absolute-fill">
|
||||||
|
<div fxFlex fxLayout="column" class="mat-elevation-z1 tb-entity-table-content">
|
||||||
|
<mat-toolbar class="mat-table-toolbar">
|
||||||
|
<div class="mat-toolbar-tools">
|
||||||
|
<div fxLayout="row" fxLayoutAlign="start center" fxLayout.xs="column" fxLayoutAlign.xs="center start" class="title-container">
|
||||||
|
<span class="tb-entity-table-title">{{(singleEntityMode ? 'version-control.entity-versions' : 'version-control.versions') | translate}}</span>
|
||||||
|
<tb-branch-autocomplete
|
||||||
|
#branchAutocompleteComponent
|
||||||
|
[selectionMode]="true"
|
||||||
|
[selectDefaultBranch]="false"
|
||||||
|
[disabled]="isLoading$ | async"
|
||||||
|
[ngModel]="branch"
|
||||||
|
(ngModelChange)="branchChanged($event)">
|
||||||
|
</tb-branch-autocomplete>
|
||||||
|
</div>
|
||||||
|
<span fxFlex></span>
|
||||||
|
<button *ngIf="singleEntityMode" mat-raised-button color="primary"
|
||||||
|
[disabled]="(isLoading$ | async)"
|
||||||
|
(click)="vcExport($event)">
|
||||||
|
{{'version-control.export-to-git' | translate }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</mat-toolbar>
|
||||||
|
<div fxFlex class="table-container">
|
||||||
|
<table mat-table [dataSource]="dataSource"
|
||||||
|
matSort [matSortActive]="pageLink.sortOrder.property" [matSortDirection]="pageLink.sortDirection()" matSortDisableClear>
|
||||||
|
<ng-container matColumnDef="timestamp">
|
||||||
|
<mat-header-cell *matHeaderCellDef mat-sort-header style="min-width: 150px; max-width: 150px; width: 150px;"> {{ 'version-control.created-time' | translate }} </mat-header-cell>
|
||||||
|
<mat-cell *matCellDef="let entityVersion">
|
||||||
|
{{ entityVersion.timestamp | date:'yyyy-MM-dd HH:mm:ss' }}
|
||||||
|
</mat-cell>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="id">
|
||||||
|
<mat-header-cell *matHeaderCellDef style="width: 40%"> {{ 'version-control.version-id' | translate }} </mat-header-cell>
|
||||||
|
<mat-cell *matCellDef="let entityVersion">
|
||||||
|
{{ entityVersion.id }}
|
||||||
|
</mat-cell>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="name">
|
||||||
|
<mat-header-cell *matHeaderCellDef style="width: 60%"> {{ 'version-control.version-name' | translate }} </mat-header-cell>
|
||||||
|
<mat-cell *matCellDef="let entityVersion">
|
||||||
|
{{ entityVersion.name }}
|
||||||
|
</mat-cell>
|
||||||
|
</ng-container>
|
||||||
|
<mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></mat-header-row>
|
||||||
|
<mat-row *matRowDef="let entityVersion; columns: displayedColumns;"></mat-row>
|
||||||
|
</table>
|
||||||
|
<span [fxShow]="dataSource.isEmpty() | async"
|
||||||
|
fxLayoutAlign="center center"
|
||||||
|
class="no-data-found" translate>{{
|
||||||
|
singleEntityMode
|
||||||
|
? 'version-control.no-entity-versions-text'
|
||||||
|
: 'version-control.no-versions-text'
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
<mat-divider></mat-divider>
|
||||||
|
<mat-paginator [length]="dataSource.total() | async"
|
||||||
|
[pageIndex]="pageLink.page"
|
||||||
|
[pageSize]="pageLink.pageSize"
|
||||||
|
[pageSizeOptions]="[10, 20, 30]"
|
||||||
|
[hidePageSize]="hidePageSize"
|
||||||
|
showFirstLastButtons></mat-paginator>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@ -0,0 +1,100 @@
|
|||||||
|
/**
|
||||||
|
* Copyright © 2016-2022 The Thingsboard Authors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
@import '../../../../../scss/constants';
|
||||||
|
|
||||||
|
:host {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: block;
|
||||||
|
.tb-entity-table {
|
||||||
|
.tb-entity-table-content {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: #fff;
|
||||||
|
|
||||||
|
.mat-toolbar-tools{
|
||||||
|
min-height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-container{
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tb-entity-table-title {
|
||||||
|
padding-right: 20px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-container {
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tb-entity-table-info{
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-widget-action{
|
||||||
|
margin-left: auto;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media #{$mat-xs} {
|
||||||
|
.mat-toolbar {
|
||||||
|
height: auto;
|
||||||
|
min-height: 100px;
|
||||||
|
|
||||||
|
.tb-entity-table-title{
|
||||||
|
padding-bottom: 5px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:host ::ng-deep {
|
||||||
|
.mat-sort-header-sorted .mat-sort-header-arrow {
|
||||||
|
opacity: 1 !important;
|
||||||
|
}
|
||||||
|
tb-branch-autocomplete {
|
||||||
|
mat-form-field {
|
||||||
|
font-size: 16px;
|
||||||
|
width: 200px;
|
||||||
|
|
||||||
|
.mat-form-field-wrapper {
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-form-field-underline {
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media #{$mat-xs} {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.mat-form-field-infix {
|
||||||
|
width: auto !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,244 @@
|
|||||||
|
///
|
||||||
|
/// Copyright © 2016-2022 The Thingsboard Authors
|
||||||
|
///
|
||||||
|
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
/// you may not use this file except in compliance with the License.
|
||||||
|
/// You may obtain a copy of the License at
|
||||||
|
///
|
||||||
|
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
///
|
||||||
|
/// Unless required by applicable law or agreed to in writing, software
|
||||||
|
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
/// See the License for the specific language governing permissions and
|
||||||
|
/// limitations under the License.
|
||||||
|
///
|
||||||
|
|
||||||
|
import {
|
||||||
|
AfterViewInit,
|
||||||
|
ChangeDetectorRef,
|
||||||
|
Component,
|
||||||
|
ElementRef,
|
||||||
|
Input,
|
||||||
|
OnDestroy,
|
||||||
|
OnInit,
|
||||||
|
ViewChild
|
||||||
|
} from '@angular/core';
|
||||||
|
import { PageComponent } from '@shared/components/page.component';
|
||||||
|
import { Store } from '@ngrx/store';
|
||||||
|
import { AppState } from '@core/core.state';
|
||||||
|
import { EntityId } from '@shared/models/id/entity-id';
|
||||||
|
import { CollectionViewer, DataSource } from '@angular/cdk/collections';
|
||||||
|
import { BehaviorSubject, merge, Observable, of, ReplaySubject } from 'rxjs';
|
||||||
|
import { emptyPageData, PageData } from '@shared/models/page/page-data';
|
||||||
|
import { PageLink } from '@shared/models/page/page-link';
|
||||||
|
import { catchError, map, tap } from 'rxjs/operators';
|
||||||
|
import { EntityVersion } from '@shared/models/vc.models';
|
||||||
|
import { EntitiesVersionControlService } from '@core/http/entities-version-control.service';
|
||||||
|
import { MatPaginator } from '@angular/material/paginator';
|
||||||
|
import { MatSort } from '@angular/material/sort';
|
||||||
|
import { ResizeObserver } from '@juggle/resize-observer';
|
||||||
|
import { hidePageSizePixelValue } from '@shared/models/constants';
|
||||||
|
import { Direction, SortOrder } from '@shared/models/page/sort-order';
|
||||||
|
import { BranchAutocompleteComponent } from '@shared/components/vc/branch-autocomplete.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'tb-entity-versions-table',
|
||||||
|
templateUrl: './entity-versions-table.component.html',
|
||||||
|
styleUrls: ['./entity-versions-table.component.scss']
|
||||||
|
})
|
||||||
|
export class EntityVersionsTableComponent extends PageComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||||
|
|
||||||
|
@ViewChild('branchAutocompleteComponent') branchAutocompleteComponent: BranchAutocompleteComponent;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
singleEntityMode = false;
|
||||||
|
|
||||||
|
displayedColumns = ['timestamp', 'id', 'name'];
|
||||||
|
pageLink: PageLink;
|
||||||
|
dataSource: EntityVersionsDatasource;
|
||||||
|
hidePageSize = false;
|
||||||
|
|
||||||
|
branch: string = null;
|
||||||
|
|
||||||
|
activeValue = false;
|
||||||
|
dirtyValue = false;
|
||||||
|
externalEntityIdValue: EntityId;
|
||||||
|
|
||||||
|
viewsInited = false;
|
||||||
|
|
||||||
|
private componentResize$: ResizeObserver;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
set active(active: boolean) {
|
||||||
|
if (this.activeValue !== active) {
|
||||||
|
this.activeValue = active;
|
||||||
|
if (this.activeValue && this.dirtyValue) {
|
||||||
|
this.dirtyValue = false;
|
||||||
|
if (this.viewsInited) {
|
||||||
|
this.initFromDefaultBranch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
set externalEntityId(externalEntityId: EntityId) {
|
||||||
|
if (this.externalEntityIdValue !== externalEntityId) {
|
||||||
|
this.externalEntityIdValue = externalEntityId;
|
||||||
|
this.resetSortAndFilter(this.activeValue);
|
||||||
|
if (!this.activeValue) {
|
||||||
|
this.dirtyValue = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewChild(MatPaginator) paginator: MatPaginator;
|
||||||
|
@ViewChild(MatSort) sort: MatSort;
|
||||||
|
|
||||||
|
constructor(protected store: Store<AppState>,
|
||||||
|
private entitiesVersionControlService: EntitiesVersionControlService,
|
||||||
|
private cd: ChangeDetectorRef,
|
||||||
|
private elementRef: ElementRef) {
|
||||||
|
super(store);
|
||||||
|
this.dirtyValue = !this.activeValue;
|
||||||
|
const sortOrder: SortOrder = { property: 'timestamp', direction: Direction.DESC };
|
||||||
|
this.pageLink = new PageLink(10, 0, null, sortOrder);
|
||||||
|
this.dataSource = new EntityVersionsDatasource(this.entitiesVersionControlService);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.componentResize$ = new ResizeObserver(() => {
|
||||||
|
const showHidePageSize = this.elementRef.nativeElement.offsetWidth < hidePageSizePixelValue;
|
||||||
|
if (showHidePageSize !== this.hidePageSize) {
|
||||||
|
this.hidePageSize = showHidePageSize;
|
||||||
|
this.cd.markForCheck();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.componentResize$.observe(this.elementRef.nativeElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
if (this.componentResize$) {
|
||||||
|
this.componentResize$.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
branchChanged(newBranch: string) {
|
||||||
|
this.branch = newBranch;
|
||||||
|
this.paginator.pageIndex = 0;
|
||||||
|
if (this.activeValue) {
|
||||||
|
this.updateData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngAfterViewInit() {
|
||||||
|
this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0);
|
||||||
|
merge(this.sort.sortChange, this.paginator.page)
|
||||||
|
.pipe(
|
||||||
|
tap(() => this.updateData())
|
||||||
|
)
|
||||||
|
.subscribe();
|
||||||
|
this.viewsInited = true;
|
||||||
|
if (!this.singleEntityMode) {
|
||||||
|
this.initFromDefaultBranch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vcExport($event: Event) {
|
||||||
|
if ($event) {
|
||||||
|
$event.stopPropagation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private initFromDefaultBranch() {
|
||||||
|
this.branchAutocompleteComponent.selectDefaultBranchIfNeeded(false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateData() {
|
||||||
|
this.pageLink.page = this.paginator.pageIndex;
|
||||||
|
this.pageLink.pageSize = this.paginator.pageSize;
|
||||||
|
this.pageLink.sortOrder.property = this.sort.active;
|
||||||
|
this.pageLink.sortOrder.direction = Direction[this.sort.direction.toUpperCase()];
|
||||||
|
this.dataSource.loadEntityVersions(this.singleEntityMode, this.branch, this.externalEntityIdValue, this.pageLink);
|
||||||
|
}
|
||||||
|
|
||||||
|
private resetSortAndFilter(update: boolean) {
|
||||||
|
this.branch = null;
|
||||||
|
this.pageLink.textSearch = null;
|
||||||
|
if (this.viewsInited) {
|
||||||
|
this.paginator.pageIndex = 0;
|
||||||
|
const sortable = this.sort.sortables.get('timestamp');
|
||||||
|
this.sort.active = sortable.id;
|
||||||
|
this.sort.direction = 'desc';
|
||||||
|
if (update) {
|
||||||
|
this.initFromDefaultBranch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EntityVersionsDatasource implements DataSource<EntityVersion> {
|
||||||
|
|
||||||
|
private entityVersionsSubject = new BehaviorSubject<EntityVersion[]>([]);
|
||||||
|
private pageDataSubject = new BehaviorSubject<PageData<EntityVersion>>(emptyPageData<EntityVersion>());
|
||||||
|
|
||||||
|
public pageData$ = this.pageDataSubject.asObservable();
|
||||||
|
|
||||||
|
constructor(private entitiesVersionControlService: EntitiesVersionControlService) {}
|
||||||
|
|
||||||
|
connect(collectionViewer: CollectionViewer): Observable<EntityVersion[] | ReadonlyArray<EntityVersion>> {
|
||||||
|
return this.entityVersionsSubject.asObservable();
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnect(collectionViewer: CollectionViewer): void {
|
||||||
|
this.entityVersionsSubject.complete();
|
||||||
|
this.pageDataSubject.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
loadEntityVersions(singleEntityMode: boolean,
|
||||||
|
branch: string, externalEntityId: EntityId,
|
||||||
|
pageLink: PageLink): Observable<PageData<EntityVersion>> {
|
||||||
|
const result = new ReplaySubject<PageData<EntityVersion>>();
|
||||||
|
this.fetchEntityVersions(singleEntityMode, branch, externalEntityId, pageLink).pipe(
|
||||||
|
catchError(() => of(emptyPageData<EntityVersion>())),
|
||||||
|
).subscribe(
|
||||||
|
(pageData) => {
|
||||||
|
this.entityVersionsSubject.next(pageData.data);
|
||||||
|
this.pageDataSubject.next(pageData);
|
||||||
|
result.next(pageData);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchEntityVersions(singleEntityMode: boolean,
|
||||||
|
branch: string, externalEntityId: EntityId,
|
||||||
|
pageLink: PageLink): Observable<PageData<EntityVersion>> {
|
||||||
|
if (!branch) {
|
||||||
|
return of(emptyPageData<EntityVersion>());
|
||||||
|
} else {
|
||||||
|
if (singleEntityMode) {
|
||||||
|
if (externalEntityId) {
|
||||||
|
return this.entitiesVersionControlService.listEntityVersions(pageLink, branch, externalEntityId, {ignoreErrors: true});
|
||||||
|
} else {
|
||||||
|
return of(emptyPageData<EntityVersion>());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return this.entitiesVersionControlService.listVersions(pageLink, branch, {ignoreErrors: true});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isEmpty(): Observable<boolean> {
|
||||||
|
return this.entityVersionsSubject.pipe(
|
||||||
|
map((entityVersions) => !entityVersions.length)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
total(): Observable<number> {
|
||||||
|
return this.pageDataSubject.pipe(
|
||||||
|
map((pageData) => pageData.totalElements)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
-->
|
-->
|
||||||
<div>
|
<div>
|
||||||
<mat-card class="settings-card">
|
<mat-card class="vc-settings" [ngClass]="{'settings-card': !detailsMode}">
|
||||||
<mat-card-title>
|
<mat-card-title>
|
||||||
<div fxLayout="row">
|
<div fxLayout="row">
|
||||||
<span class="mat-headline" translate>admin.git-repository-settings</span>
|
<span class="mat-headline" translate>admin.git-repository-settings</span>
|
||||||
@ -14,6 +14,9 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
:host {
|
:host {
|
||||||
|
mat-card.vc-settings {
|
||||||
|
margin: 8px;
|
||||||
|
}
|
||||||
.fields-group {
|
.fields-group {
|
||||||
padding: 0 16px 8px;
|
padding: 0 16px 8px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
@ -14,11 +14,11 @@
|
|||||||
/// limitations under the License.
|
/// limitations under the License.
|
||||||
///
|
///
|
||||||
|
|
||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
import { PageComponent } from '@shared/components/page.component';
|
import { PageComponent } from '@shared/components/page.component';
|
||||||
import { HasConfirmForm } from '@core/guards/confirm-on-exit.guard';
|
import { HasConfirmForm } from '@core/guards/confirm-on-exit.guard';
|
||||||
import { FormBuilder, FormGroup, FormGroupDirective, Validators } from '@angular/forms';
|
import { FormBuilder, FormGroup, FormGroupDirective, Validators } from '@angular/forms';
|
||||||
import { Store } from '@ngrx/store';
|
import { select, Store } from '@ngrx/store';
|
||||||
import { AppState } from '@core/core.state';
|
import { AppState } from '@core/core.state';
|
||||||
import { AdminService } from '@core/http/admin.service';
|
import { AdminService } from '@core/http/admin.service';
|
||||||
import {
|
import {
|
||||||
@ -30,13 +30,21 @@ import { ActionNotificationShow } from '@core/notification/notification.actions'
|
|||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { isNotEmptyStr } from '@core/utils';
|
import { isNotEmptyStr } from '@core/utils';
|
||||||
import { DialogService } from '@core/services/dialog.service';
|
import { DialogService } from '@core/services/dialog.service';
|
||||||
|
import { ActionSettingsChangeLanguage } from '@core/settings/settings.actions';
|
||||||
|
import { ActionAuthUpdateHasVersionControl } from '@core/auth/auth.actions';
|
||||||
|
import { selectHasVersionControl } from '@core/auth/auth.selectors';
|
||||||
|
import { catchError, mergeMap } from 'rxjs/operators';
|
||||||
|
import { of } from 'rxjs';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'tb-version-control-settings',
|
selector: 'tb-version-control-settings',
|
||||||
templateUrl: './version-control-settings.component.html',
|
templateUrl: './version-control-settings.component.html',
|
||||||
styleUrls: ['./version-control-settings.component.scss', './settings-card.scss']
|
styleUrls: ['./version-control-settings.component.scss', './../../pages/admin/settings-card.scss']
|
||||||
})
|
})
|
||||||
export class VersionControlSettingsComponent extends PageComponent implements OnInit, HasConfirmForm {
|
export class VersionControlSettingsComponent extends PageComponent implements OnInit {
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
detailsMode = false;
|
||||||
|
|
||||||
versionControlSettingsForm: FormGroup;
|
versionControlSettingsForm: FormGroup;
|
||||||
settings: EntitiesVersionControlSettings = null;
|
settings: EntitiesVersionControlSettings = null;
|
||||||
@ -62,7 +70,7 @@ export class VersionControlSettingsComponent extends PageComponent implements On
|
|||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.versionControlSettingsForm = this.fb.group({
|
this.versionControlSettingsForm = this.fb.group({
|
||||||
repositoryUri: [null, [Validators.required]],
|
repositoryUri: [null, [Validators.required]],
|
||||||
defaultBranch: [null, []],
|
defaultBranch: ['main', []],
|
||||||
authMethod: [VersionControlAuthMethod.USERNAME_PASSWORD, [Validators.required]],
|
authMethod: [VersionControlAuthMethod.USERNAME_PASSWORD, [Validators.required]],
|
||||||
username: [null, []],
|
username: [null, []],
|
||||||
password: [null, []],
|
password: [null, []],
|
||||||
@ -77,16 +85,29 @@ export class VersionControlSettingsComponent extends PageComponent implements On
|
|||||||
this.versionControlSettingsForm.get('privateKeyFileName').valueChanges.subscribe(() => {
|
this.versionControlSettingsForm.get('privateKeyFileName').valueChanges.subscribe(() => {
|
||||||
this.updateValidators(false);
|
this.updateValidators(false);
|
||||||
});
|
});
|
||||||
this.adminService.getEntitiesVersionControlSettings({ignoreErrors: true}).subscribe(
|
this.store.pipe(
|
||||||
|
select(selectHasVersionControl),
|
||||||
|
mergeMap((hasVersionControl) => {
|
||||||
|
if (hasVersionControl) {
|
||||||
|
return this.adminService.getEntitiesVersionControlSettings({ignoreErrors: true}).pipe(
|
||||||
|
catchError(() => of(null))
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return of(null);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
).subscribe(
|
||||||
(settings) => {
|
(settings) => {
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
if (this.settings.authMethod === VersionControlAuthMethod.USERNAME_PASSWORD) {
|
if (this.settings != null) {
|
||||||
this.showChangePassword = true;
|
if (this.settings.authMethod === VersionControlAuthMethod.USERNAME_PASSWORD) {
|
||||||
} else {
|
this.showChangePassword = true;
|
||||||
this.showChangePrivateKeyPassword = true;
|
} else {
|
||||||
|
this.showChangePrivateKeyPassword = true;
|
||||||
|
}
|
||||||
|
this.versionControlSettingsForm.reset(this.settings);
|
||||||
|
this.updateValidators(false);
|
||||||
}
|
}
|
||||||
this.versionControlSettingsForm.reset(this.settings);
|
|
||||||
this.updateValidators(false);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,6 +133,7 @@ export class VersionControlSettingsComponent extends PageComponent implements On
|
|||||||
}
|
}
|
||||||
this.versionControlSettingsForm.reset(this.settings);
|
this.versionControlSettingsForm.reset(this.settings);
|
||||||
this.updateValidators(false);
|
this.updateValidators(false);
|
||||||
|
this.store.dispatch(new ActionAuthUpdateHasVersionControl({ hasVersionControl: true }));
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -131,18 +153,15 @@ export class VersionControlSettingsComponent extends PageComponent implements On
|
|||||||
this.showChangePrivateKeyPassword = false;
|
this.showChangePrivateKeyPassword = false;
|
||||||
this.changePrivateKeyPassword = false;
|
this.changePrivateKeyPassword = false;
|
||||||
formDirective.resetForm();
|
formDirective.resetForm();
|
||||||
this.versionControlSettingsForm.reset({ authMethod: VersionControlAuthMethod.USERNAME_PASSWORD });
|
this.versionControlSettingsForm.reset({ defaultBranch: 'main', authMethod: VersionControlAuthMethod.USERNAME_PASSWORD });
|
||||||
this.updateValidators(false);
|
this.updateValidators(false);
|
||||||
|
this.store.dispatch(new ActionAuthUpdateHasVersionControl({ hasVersionControl: false }));
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
confirmForm(): FormGroup {
|
|
||||||
return this.versionControlSettingsForm;
|
|
||||||
}
|
|
||||||
|
|
||||||
changePasswordChanged() {
|
changePasswordChanged() {
|
||||||
if (this.changePassword) {
|
if (this.changePassword) {
|
||||||
this.versionControlSettingsForm.get('password').patchValue('');
|
this.versionControlSettingsForm.get('password').patchValue('');
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
<!--
|
||||||
|
|
||||||
|
Copyright © 2016-2022 The Thingsboard Authors
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|
||||||
|
-->
|
||||||
|
<tb-version-control-settings #versionControlSettingsComponent [detailsMode]="detailsMode"
|
||||||
|
*ngIf="!(hasVersionControl$ | async); else versionsTable">
|
||||||
|
</tb-version-control-settings>
|
||||||
|
<ng-template #versionsTable>
|
||||||
|
<tb-entity-versions-table [singleEntityMode]="singleEntityMode"
|
||||||
|
[active]="active"
|
||||||
|
[externalEntityId]="externalEntityId"></tb-entity-versions-table>
|
||||||
|
</ng-template>
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* Copyright © 2016-2022 The Thingsboard Authors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
:host {
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,61 @@
|
|||||||
|
///
|
||||||
|
/// Copyright © 2016-2022 The Thingsboard Authors
|
||||||
|
///
|
||||||
|
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
/// you may not use this file except in compliance with the License.
|
||||||
|
/// You may obtain a copy of the License at
|
||||||
|
///
|
||||||
|
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
///
|
||||||
|
/// Unless required by applicable law or agreed to in writing, software
|
||||||
|
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
/// See the License for the specific language governing permissions and
|
||||||
|
/// limitations under the License.
|
||||||
|
///
|
||||||
|
|
||||||
|
import { Component, Input, OnInit, ViewChild } from '@angular/core';
|
||||||
|
import { select, Store } from '@ngrx/store';
|
||||||
|
import { AppState } from '@core/core.state';
|
||||||
|
import { selectHasVersionControl } from '@core/auth/auth.selectors';
|
||||||
|
import { HasConfirmForm } from '@core/guards/confirm-on-exit.guard';
|
||||||
|
import { VersionControlSettingsComponent } from '@home/components/vc/version-control-settings.component';
|
||||||
|
import { FormGroup } from '@angular/forms';
|
||||||
|
import { EntityId } from '@shared/models/id/entity-id';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'tb-version-control',
|
||||||
|
templateUrl: './version-control.component.html',
|
||||||
|
styleUrls: ['./version-control.component.scss']
|
||||||
|
})
|
||||||
|
export class VersionControlComponent implements OnInit, HasConfirmForm {
|
||||||
|
|
||||||
|
@ViewChild('versionControlSettingsComponent', {static: false}) versionControlSettingsComponent: VersionControlSettingsComponent;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
detailsMode = false;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
active = true;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
singleEntityMode = false;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
externalEntityId: EntityId;
|
||||||
|
|
||||||
|
hasVersionControl$ = this.store.pipe(select(selectHasVersionControl));
|
||||||
|
|
||||||
|
constructor(private store: Store<AppState>) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
confirmForm(): FormGroup {
|
||||||
|
return this.versionControlSettingsComponent?.versionControlSettingsForm;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -33,7 +33,7 @@ import { EntityDetailsPageComponent } from '@home/components/entity/entity-detai
|
|||||||
import { entityDetailsPageBreadcrumbLabelFunction } from '@home/pages/home-pages.models';
|
import { entityDetailsPageBreadcrumbLabelFunction } from '@home/pages/home-pages.models';
|
||||||
import { BreadCrumbConfig } from '@shared/components/breadcrumb';
|
import { BreadCrumbConfig } from '@shared/components/breadcrumb';
|
||||||
import { QueuesTableConfigResolver } from '@home/pages/admin/queue/queues-table-config.resolver';
|
import { QueuesTableConfigResolver } from '@home/pages/admin/queue/queues-table-config.resolver';
|
||||||
import { VersionControlSettingsComponent } from '@home/pages/admin/version-control-settings.component';
|
import { VersionControlAdminSettingsComponent } from '@home/pages/admin/version-control-admin-settings.component';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class OAuth2LoginProcessingUrlResolver implements Resolve<string> {
|
export class OAuth2LoginProcessingUrlResolver implements Resolve<string> {
|
||||||
@ -226,7 +226,7 @@ const routes: Routes = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'vc',
|
path: 'vc',
|
||||||
component: VersionControlSettingsComponent,
|
component: VersionControlAdminSettingsComponent,
|
||||||
canDeactivate: [ConfirmOnExitGuard],
|
canDeactivate: [ConfirmOnExitGuard],
|
||||||
data: {
|
data: {
|
||||||
auth: [Authority.TENANT_ADMIN],
|
auth: [Authority.TENANT_ADMIN],
|
||||||
|
|||||||
@ -29,7 +29,7 @@ import { SendTestSmsDialogComponent } from '@home/pages/admin/send-test-sms-dial
|
|||||||
import { HomeSettingsComponent } from '@home/pages/admin/home-settings.component';
|
import { HomeSettingsComponent } from '@home/pages/admin/home-settings.component';
|
||||||
import { ResourcesLibraryComponent } from '@home/pages/admin/resource/resources-library.component';
|
import { ResourcesLibraryComponent } from '@home/pages/admin/resource/resources-library.component';
|
||||||
import { QueueComponent} from '@home/pages/admin/queue/queue.component';
|
import { QueueComponent} from '@home/pages/admin/queue/queue.component';
|
||||||
import { VersionControlSettingsComponent } from '@home/pages/admin/version-control-settings.component';
|
import { VersionControlAdminSettingsComponent } from '@home/pages/admin/version-control-admin-settings.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations:
|
declarations:
|
||||||
@ -43,7 +43,7 @@ import { VersionControlSettingsComponent } from '@home/pages/admin/version-contr
|
|||||||
HomeSettingsComponent,
|
HomeSettingsComponent,
|
||||||
ResourcesLibraryComponent,
|
ResourcesLibraryComponent,
|
||||||
QueueComponent,
|
QueueComponent,
|
||||||
VersionControlSettingsComponent
|
VersionControlAdminSettingsComponent
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
|
|||||||
@ -0,0 +1,18 @@
|
|||||||
|
<!--
|
||||||
|
|
||||||
|
Copyright © 2016-2022 The Thingsboard Authors
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|
||||||
|
-->
|
||||||
|
<tb-version-control-settings #versionControlSettingsComponent></tb-version-control-settings>
|
||||||
@ -0,0 +1,44 @@
|
|||||||
|
///
|
||||||
|
/// Copyright © 2016-2022 The Thingsboard Authors
|
||||||
|
///
|
||||||
|
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
/// you may not use this file except in compliance with the License.
|
||||||
|
/// You may obtain a copy of the License at
|
||||||
|
///
|
||||||
|
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
///
|
||||||
|
/// Unless required by applicable law or agreed to in writing, software
|
||||||
|
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
/// See the License for the specific language governing permissions and
|
||||||
|
/// limitations under the License.
|
||||||
|
///
|
||||||
|
|
||||||
|
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||||
|
import { PageComponent } from '@shared/components/page.component';
|
||||||
|
import { HasConfirmForm } from '@core/guards/confirm-on-exit.guard';
|
||||||
|
import { Store } from '@ngrx/store';
|
||||||
|
import { AppState } from '@core/core.state';
|
||||||
|
import { FormGroup } from '@angular/forms';
|
||||||
|
import { VersionControlSettingsComponent } from '@home/components/vc/version-control-settings.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'tb-version-control-admin-settings',
|
||||||
|
templateUrl: './version-control-admin-settings.component.html',
|
||||||
|
styleUrls: []
|
||||||
|
})
|
||||||
|
export class VersionControlAdminSettingsComponent extends PageComponent implements OnInit, HasConfirmForm {
|
||||||
|
|
||||||
|
@ViewChild('versionControlSettingsComponent') versionControlSettingsComponent: VersionControlSettingsComponent;
|
||||||
|
|
||||||
|
constructor(protected store: Store<AppState>) {
|
||||||
|
super(store);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
}
|
||||||
|
|
||||||
|
confirmForm(): FormGroup {
|
||||||
|
return this.versionControlSettingsComponent?.versionControlSettingsForm;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -49,3 +49,8 @@
|
|||||||
label="{{ 'audit-log.audit-logs' | translate }}" #auditLogsTab="matTab">
|
label="{{ 'audit-log.audit-logs' | translate }}" #auditLogsTab="matTab">
|
||||||
<tb-audit-log-table detailsMode="true" [active]="auditLogsTab.isActive" [auditLogMode]="auditLogModes.ENTITY" [entityId]="entity.id"></tb-audit-log-table>
|
<tb-audit-log-table detailsMode="true" [active]="auditLogsTab.isActive" [auditLogMode]="auditLogModes.ENTITY" [entityId]="entity.id"></tb-audit-log-table>
|
||||||
</mat-tab>
|
</mat-tab>
|
||||||
|
<mat-tab *ngIf="entity && authUser.authority === authorities.TENANT_ADMIN"
|
||||||
|
label="{{ 'version-control.version-control' | translate }}" #versionControlTab="matTab">
|
||||||
|
<tb-version-control detailsMode="true" singleEntityMode="true"
|
||||||
|
[active]="versionControlTab.isActive" [externalEntityId]="entity.externalId || entity.id"></tb-version-control>
|
||||||
|
</mat-tab>
|
||||||
|
|||||||
@ -36,6 +36,7 @@ import { DeviceProfileModule } from './device-profile/device-profile.module';
|
|||||||
import { ApiUsageModule } from '@home/pages/api-usage/api-usage.module';
|
import { ApiUsageModule } from '@home/pages/api-usage/api-usage.module';
|
||||||
import { EdgeModule } from '@home/pages/edge/edge.module';
|
import { EdgeModule } from '@home/pages/edge/edge.module';
|
||||||
import { OtaUpdateModule } from '@home/pages/ota-update/ota-update.module';
|
import { OtaUpdateModule } from '@home/pages/ota-update/ota-update.module';
|
||||||
|
import { VcModule } from '@home/pages/vc/vc.module';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
exports: [
|
exports: [
|
||||||
@ -56,7 +57,8 @@ import { OtaUpdateModule } from '@home/pages/ota-update/ota-update.module';
|
|||||||
AuditLogModule,
|
AuditLogModule,
|
||||||
ApiUsageModule,
|
ApiUsageModule,
|
||||||
OtaUpdateModule,
|
OtaUpdateModule,
|
||||||
UserModule
|
UserModule,
|
||||||
|
VcModule
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
{
|
{
|
||||||
|
|||||||
44
ui-ngx/src/app/modules/home/pages/vc/vc-routing.module.ts
Normal file
44
ui-ngx/src/app/modules/home/pages/vc/vc-routing.module.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
///
|
||||||
|
/// Copyright © 2016-2022 The Thingsboard Authors
|
||||||
|
///
|
||||||
|
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
/// you may not use this file except in compliance with the License.
|
||||||
|
/// You may obtain a copy of the License at
|
||||||
|
///
|
||||||
|
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
///
|
||||||
|
/// Unless required by applicable law or agreed to in writing, software
|
||||||
|
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
/// See the License for the specific language governing permissions and
|
||||||
|
/// limitations under the License.
|
||||||
|
///
|
||||||
|
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard';
|
||||||
|
import { Authority } from '@shared/models/authority.enum';
|
||||||
|
import { VersionControlComponent } from '@home/components/vc/version-control.component';
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{
|
||||||
|
path: 'vc',
|
||||||
|
component: VersionControlComponent,
|
||||||
|
canDeactivate: [ConfirmOnExitGuard],
|
||||||
|
data: {
|
||||||
|
auth: [Authority.TENANT_ADMIN],
|
||||||
|
title: 'version-control.version-control',
|
||||||
|
breadcrumb: {
|
||||||
|
label: 'version-control.version-control',
|
||||||
|
icon: 'history'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [RouterModule.forChild(routes)],
|
||||||
|
exports: [RouterModule],
|
||||||
|
providers: []
|
||||||
|
})
|
||||||
|
export class VcRoutingModule { }
|
||||||
31
ui-ngx/src/app/modules/home/pages/vc/vc.module.ts
Normal file
31
ui-ngx/src/app/modules/home/pages/vc/vc.module.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
///
|
||||||
|
/// Copyright © 2016-2022 The Thingsboard Authors
|
||||||
|
///
|
||||||
|
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
/// you may not use this file except in compliance with the License.
|
||||||
|
/// You may obtain a copy of the License at
|
||||||
|
///
|
||||||
|
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
///
|
||||||
|
/// Unless required by applicable law or agreed to in writing, software
|
||||||
|
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
/// See the License for the specific language governing permissions and
|
||||||
|
/// limitations under the License.
|
||||||
|
///
|
||||||
|
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { SharedModule } from '@shared/shared.module';
|
||||||
|
import { VcRoutingModule } from '@home/pages/vc/vc-routing.module';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
SharedModule,
|
||||||
|
VcRoutingModule
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class VcModule { }
|
||||||
@ -15,7 +15,7 @@
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
|
|
||||||
-->
|
-->
|
||||||
<mat-form-field [formGroup]="branchFormGroup" class="mat-block">
|
<mat-form-field [formGroup]="branchFormGroup" class="mat-block" [floatLabel]="selectionMode ? 'always' : 'auto'">
|
||||||
<mat-label>{{ 'version-control.branch' | translate }}</mat-label>
|
<mat-label>{{ 'version-control.branch' | translate }}</mat-label>
|
||||||
<input matInput type="text" placeholder="{{ 'version-control.select-branch' | translate }}"
|
<input matInput type="text" placeholder="{{ 'version-control.select-branch' | translate }}"
|
||||||
#branchInput
|
#branchInput
|
||||||
@ -31,10 +31,11 @@
|
|||||||
</button>
|
</button>
|
||||||
<mat-autocomplete
|
<mat-autocomplete
|
||||||
class="tb-autocomplete"
|
class="tb-autocomplete"
|
||||||
|
(closed)="onPanelClosed()"
|
||||||
#subTypeAutocomplete="matAutocomplete"
|
#subTypeAutocomplete="matAutocomplete"
|
||||||
[displayWith]="displayBranchFn">
|
[displayWith]="displayBranchFn">
|
||||||
<mat-option *ngFor="let branch of filteredBranches | async" [value]="branch">
|
<mat-option *ngFor="let branch of filteredBranches | async" [value]="branch">
|
||||||
<span [innerHTML]="branch | highlight:searchText"></span>
|
<span [innerHTML]="branch.name | highlight:searchText"></span>
|
||||||
</mat-option>
|
</mat-option>
|
||||||
</mat-autocomplete>
|
</mat-autocomplete>
|
||||||
<mat-error *ngIf="branchFormGroup.get('branch').hasError('required')">
|
<mat-error *ngIf="branchFormGroup.get('branch').hasError('required')">
|
||||||
|
|||||||
@ -14,7 +14,17 @@
|
|||||||
/// limitations under the License.
|
/// limitations under the License.
|
||||||
///
|
///
|
||||||
|
|
||||||
import { AfterViewInit, Component, ElementRef, forwardRef, Input, OnInit, ViewChild } from '@angular/core';
|
import {
|
||||||
|
AfterViewInit,
|
||||||
|
ChangeDetectorRef,
|
||||||
|
Component,
|
||||||
|
ElementRef,
|
||||||
|
forwardRef,
|
||||||
|
Input,
|
||||||
|
NgZone,
|
||||||
|
OnInit,
|
||||||
|
ViewChild
|
||||||
|
} from '@angular/core';
|
||||||
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms';
|
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||||
import { Observable, of } from 'rxjs';
|
import { Observable, of } from 'rxjs';
|
||||||
import {
|
import {
|
||||||
@ -24,6 +34,7 @@ import {
|
|||||||
map,
|
map,
|
||||||
publishReplay,
|
publishReplay,
|
||||||
refCount,
|
refCount,
|
||||||
|
share,
|
||||||
switchMap,
|
switchMap,
|
||||||
tap
|
tap
|
||||||
} from 'rxjs/operators';
|
} from 'rxjs/operators';
|
||||||
@ -32,6 +43,7 @@ import { AppState } from '@app/core/core.state';
|
|||||||
import { coerceBooleanProperty } from '@angular/cdk/coercion';
|
import { coerceBooleanProperty } from '@angular/cdk/coercion';
|
||||||
import { BranchInfo } from '@shared/models/vc.models';
|
import { BranchInfo } from '@shared/models/vc.models';
|
||||||
import { EntitiesVersionControlService } from '@core/http/entities-version-control.service';
|
import { EntitiesVersionControlService } from '@core/http/entities-version-control.service';
|
||||||
|
import { isNotEmptyStr } from '@core/utils';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'tb-branch-autocomplete',
|
selector: 'tb-branch-autocomplete',
|
||||||
@ -60,27 +72,48 @@ export class BranchAutocompleteComponent implements ControlValueAccessor, OnInit
|
|||||||
this.requiredValue = coerceBooleanProperty(value);
|
this.requiredValue = coerceBooleanProperty(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private disabledValue: boolean;
|
||||||
|
|
||||||
|
get disabled(): boolean {
|
||||||
|
return this.disabledValue;
|
||||||
|
}
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
disabled: boolean;
|
set disabled(value: boolean) {
|
||||||
|
this.disabledValue = coerceBooleanProperty(value);
|
||||||
|
if (this.disabledValue) {
|
||||||
|
this.branchFormGroup.disable({emitEvent: false});
|
||||||
|
} else {
|
||||||
|
this.branchFormGroup.enable({emitEvent: false});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
selectDefaultBranch = true;
|
selectDefaultBranch = true;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
selectionMode = false;
|
||||||
|
|
||||||
@ViewChild('branchInput', {static: true}) branchInput: ElementRef;
|
@ViewChild('branchInput', {static: true}) branchInput: ElementRef;
|
||||||
|
|
||||||
filteredBranches: Observable<Array<string>>;
|
filteredBranches: Observable<Array<BranchInfo>>;
|
||||||
|
|
||||||
branches: Observable<Array<BranchInfo>>;
|
branches: Observable<Array<BranchInfo>> = null;
|
||||||
|
|
||||||
|
defaultBranch: BranchInfo = null;
|
||||||
|
|
||||||
searchText = '';
|
searchText = '';
|
||||||
|
|
||||||
private dirty = false;
|
private dirty = false;
|
||||||
|
|
||||||
|
private ignoreClosedPanel = false;
|
||||||
|
|
||||||
private propagateChange = (v: any) => { };
|
private propagateChange = (v: any) => { };
|
||||||
|
|
||||||
constructor(private store: Store<AppState>,
|
constructor(private store: Store<AppState>,
|
||||||
private entitiesVersionControlService: EntitiesVersionControlService,
|
private entitiesVersionControlService: EntitiesVersionControlService,
|
||||||
private fb: FormBuilder) {
|
private fb: FormBuilder,
|
||||||
|
private zone: NgZone) {
|
||||||
this.branchFormGroup = this.fb.group({
|
this.branchFormGroup = this.fb.group({
|
||||||
branch: [null, []]
|
branch: [null, []]
|
||||||
});
|
});
|
||||||
@ -94,17 +127,36 @@ export class BranchAutocompleteComponent implements ControlValueAccessor, OnInit
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
|
||||||
this.branches = null;
|
|
||||||
this.filteredBranches = this.branchFormGroup.get('branch').valueChanges
|
this.filteredBranches = this.branchFormGroup.get('branch').valueChanges
|
||||||
.pipe(
|
.pipe(
|
||||||
|
tap((value: BranchInfo | string) => {
|
||||||
|
let modelValue: BranchInfo | null;
|
||||||
|
if (typeof value === 'string' || !value) {
|
||||||
|
if (!this.selectionMode && typeof value === 'string' && isNotEmptyStr(value)) {
|
||||||
|
modelValue = {name: value, default: false};
|
||||||
|
} else {
|
||||||
|
modelValue = null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
modelValue = value;
|
||||||
|
}
|
||||||
|
this.updateView(modelValue);
|
||||||
|
}),
|
||||||
|
map(value => {
|
||||||
|
if (value) {
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
return value;
|
||||||
|
} else {
|
||||||
|
return value.name;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}),
|
||||||
debounceTime(150),
|
debounceTime(150),
|
||||||
distinctUntilChanged(),
|
distinctUntilChanged(),
|
||||||
tap(value => {
|
switchMap(name => this.fetchBranches(name)),
|
||||||
this.updateView(value);
|
share()
|
||||||
}),
|
|
||||||
map(value => value ? value : ''),
|
|
||||||
switchMap(branch => this.fetchBranches(branch))
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,24 +165,16 @@ export class BranchAutocompleteComponent implements ControlValueAccessor, OnInit
|
|||||||
|
|
||||||
setDisabledState(isDisabled: boolean): void {
|
setDisabledState(isDisabled: boolean): void {
|
||||||
this.disabled = isDisabled;
|
this.disabled = isDisabled;
|
||||||
if (this.disabled) {
|
|
||||||
this.branchFormGroup.disable({emitEvent: false});
|
|
||||||
} else {
|
|
||||||
this.branchFormGroup.enable({emitEvent: false});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
selectDefaultBranchIfNeeded(): void {
|
selectDefaultBranchIfNeeded(ignoreLoading = true, force = false): void {
|
||||||
if (this.selectDefaultBranch && !this.modelValue) {
|
if ((this.selectDefaultBranch && !this.modelValue) || force) {
|
||||||
this.getBranches().subscribe(
|
this.getBranches(ignoreLoading).subscribe(
|
||||||
(data) => {
|
(data) => {
|
||||||
if (data && data.length) {
|
if (this.defaultBranch || force) {
|
||||||
const defaultBranch = data.find(branch => branch.default);
|
this.branchFormGroup.get('branch').patchValue(this.defaultBranch, {emitEvent: false});
|
||||||
if (defaultBranch) {
|
this.modelValue = this.defaultBranch?.name;
|
||||||
this.modelValue = defaultBranch.name;
|
this.propagateChange(this.modelValue);
|
||||||
this.branchFormGroup.get('branch').patchValue(this.modelValue, {emitEvent: false});
|
|
||||||
this.propagateChange(this.modelValue);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -141,9 +185,9 @@ export class BranchAutocompleteComponent implements ControlValueAccessor, OnInit
|
|||||||
this.searchText = '';
|
this.searchText = '';
|
||||||
this.modelValue = value;
|
this.modelValue = value;
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
this.branchFormGroup.get('branch').patchValue(value, {emitEvent: false});
|
this.branchFormGroup.get('branch').patchValue({name: value}, {emitEvent: false});
|
||||||
} else {
|
} else {
|
||||||
this.branchFormGroup.get('branch').patchValue('', {emitEvent: false});
|
this.branchFormGroup.get('branch').patchValue(null, {emitEvent: false});
|
||||||
this.selectDefaultBranchIfNeeded();
|
this.selectDefaultBranchIfNeeded();
|
||||||
}
|
}
|
||||||
this.dirty = true;
|
this.dirty = true;
|
||||||
@ -156,31 +200,53 @@ export class BranchAutocompleteComponent implements ControlValueAccessor, OnInit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateView(value: string | null) {
|
onPanelClosed() {
|
||||||
if (this.modelValue !== value) {
|
if (this.ignoreClosedPanel) {
|
||||||
this.modelValue = value;
|
this.ignoreClosedPanel = false;
|
||||||
|
} else {
|
||||||
|
if (this.selectionMode && !this.branchFormGroup.get('branch').value && this.defaultBranch) {
|
||||||
|
this.zone.run(() => {
|
||||||
|
this.branchFormGroup.get('branch').patchValue(this.defaultBranch, {emitEvent: true});
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateView(value: BranchInfo | null) {
|
||||||
|
if (this.modelValue !== value?.name) {
|
||||||
|
this.modelValue = value?.name;
|
||||||
this.propagateChange(this.modelValue);
|
this.propagateChange(this.modelValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
displayBranchFn(branch?: string): string | undefined {
|
displayBranchFn(branch?: BranchInfo): string | undefined {
|
||||||
return branch ? branch : undefined;
|
return branch ? branch.name : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchBranches(searchText?: string): Observable<Array<string>> {
|
fetchBranches(searchText?: string): Observable<Array<BranchInfo>> {
|
||||||
this.searchText = searchText;
|
this.searchText = searchText;
|
||||||
return this.getBranches().pipe(
|
return this.getBranches().pipe(
|
||||||
map(branches => branches.map(branch => branch.name).filter(branchName => {
|
map(branches => {
|
||||||
return searchText ? branchName.toUpperCase().startsWith(searchText.toUpperCase()) : true;
|
let res = branches.filter(branch => {
|
||||||
}))
|
return searchText ? branch.name.toUpperCase().startsWith(searchText.toUpperCase()) : true;
|
||||||
|
});
|
||||||
|
if (!this.selectionMode && isNotEmptyStr(searchText) && !res.find(b => b.name === searchText)) {
|
||||||
|
res = [{name: searchText, default: false}, ...res];
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getBranches(): Observable<Array<BranchInfo>> {
|
getBranches(ignoreLoading = true): Observable<Array<BranchInfo>> {
|
||||||
if (!this.branches) {
|
if (!this.branches) {
|
||||||
const branchesObservable = this.entitiesVersionControlService.listBranches({ignoreLoading: true, ignoreErrors: true});
|
const branchesObservable = this.entitiesVersionControlService.listBranches({ignoreLoading, ignoreErrors: true});
|
||||||
this.branches = branchesObservable.pipe(
|
this.branches = branchesObservable.pipe(
|
||||||
catchError(() => of([] as Array<BranchInfo>)),
|
catchError(() => of([] as Array<BranchInfo>)),
|
||||||
|
tap((data) => {
|
||||||
|
this.defaultBranch = data.find(branch => branch.default);
|
||||||
|
}),
|
||||||
publishReplay(1),
|
publishReplay(1),
|
||||||
refCount()
|
refCount()
|
||||||
);
|
);
|
||||||
@ -189,6 +255,7 @@ export class BranchAutocompleteComponent implements ControlValueAccessor, OnInit
|
|||||||
}
|
}
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
|
this.ignoreClosedPanel = true;
|
||||||
this.branchFormGroup.get('branch').patchValue(null, {emitEvent: true});
|
this.branchFormGroup.get('branch').patchValue(null, {emitEvent: true});
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.branchInput.nativeElement.blur();
|
this.branchInput.nativeElement.blur();
|
||||||
|
|||||||
@ -14,13 +14,13 @@
|
|||||||
/// limitations under the License.
|
/// limitations under the License.
|
||||||
///
|
///
|
||||||
|
|
||||||
import { BaseData } from '@shared/models/base-data';
|
import { BaseData, ExportableEntity } from '@shared/models/base-data';
|
||||||
import { AssetId } from './id/asset-id';
|
import { AssetId } from './id/asset-id';
|
||||||
import { TenantId } from '@shared/models/id/tenant-id';
|
import { TenantId } from '@shared/models/id/tenant-id';
|
||||||
import { CustomerId } from '@shared/models/id/customer-id';
|
import { CustomerId } from '@shared/models/id/customer-id';
|
||||||
import { EntitySearchQuery } from '@shared/models/relation.models';
|
import { EntitySearchQuery } from '@shared/models/relation.models';
|
||||||
|
|
||||||
export interface Asset extends BaseData<AssetId> {
|
export interface Asset extends BaseData<AssetId>, ExportableEntity<AssetId> {
|
||||||
tenantId?: TenantId;
|
tenantId?: TenantId;
|
||||||
customerId?: CustomerId;
|
customerId?: CustomerId;
|
||||||
name: string;
|
name: string;
|
||||||
|
|||||||
@ -27,6 +27,12 @@ export interface BaseData<T extends HasId> {
|
|||||||
label?: string;
|
label?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ExportableEntity<T extends EntityId> {
|
||||||
|
createdTime?: number;
|
||||||
|
id?: T;
|
||||||
|
externalId?: T;
|
||||||
|
}
|
||||||
|
|
||||||
export function hasIdEquals(id1: HasId, id2: HasId): boolean {
|
export function hasIdEquals(id1: HasId, id2: HasId): boolean {
|
||||||
if (isDefinedAndNotNull(id1) && isDefinedAndNotNull(id2)) {
|
if (isDefinedAndNotNull(id1) && isDefinedAndNotNull(id2)) {
|
||||||
return id1.id === id2.id;
|
return id1.id === id2.id;
|
||||||
|
|||||||
@ -17,8 +17,9 @@
|
|||||||
import { CustomerId } from '@shared/models/id/customer-id';
|
import { CustomerId } from '@shared/models/id/customer-id';
|
||||||
import { ContactBased } from '@shared/models/contact-based.model';
|
import { ContactBased } from '@shared/models/contact-based.model';
|
||||||
import { TenantId } from './id/tenant-id';
|
import { TenantId } from './id/tenant-id';
|
||||||
|
import { ExportableEntity } from '@shared/models/base-data';
|
||||||
|
|
||||||
export interface Customer extends ContactBased<CustomerId> {
|
export interface Customer extends ContactBased<CustomerId>, ExportableEntity<CustomerId> {
|
||||||
tenantId: TenantId;
|
tenantId: TenantId;
|
||||||
title: string;
|
title: string;
|
||||||
additionalInfo?: any;
|
additionalInfo?: any;
|
||||||
|
|||||||
@ -14,7 +14,7 @@
|
|||||||
/// limitations under the License.
|
/// limitations under the License.
|
||||||
///
|
///
|
||||||
|
|
||||||
import { BaseData } from '@shared/models/base-data';
|
import { BaseData, ExportableEntity } from '@shared/models/base-data';
|
||||||
import { DashboardId } from '@shared/models/id/dashboard-id';
|
import { DashboardId } from '@shared/models/id/dashboard-id';
|
||||||
import { TenantId } from '@shared/models/id/tenant-id';
|
import { TenantId } from '@shared/models/id/tenant-id';
|
||||||
import { ShortCustomerInfo } from '@shared/models/customer.model';
|
import { ShortCustomerInfo } from '@shared/models/customer.model';
|
||||||
@ -23,7 +23,7 @@ import { Timewindow } from '@shared/models/time/time.models';
|
|||||||
import { EntityAliases } from './alias.models';
|
import { EntityAliases } from './alias.models';
|
||||||
import { Filters } from '@shared/models/query/query.models';
|
import { Filters } from '@shared/models/query/query.models';
|
||||||
|
|
||||||
export interface DashboardInfo extends BaseData<DashboardId> {
|
export interface DashboardInfo extends BaseData<DashboardId>, ExportableEntity<DashboardId> {
|
||||||
tenantId?: TenantId;
|
tenantId?: TenantId;
|
||||||
title?: string;
|
title?: string;
|
||||||
image?: string;
|
image?: string;
|
||||||
|
|||||||
@ -14,7 +14,7 @@
|
|||||||
/// limitations under the License.
|
/// limitations under the License.
|
||||||
///
|
///
|
||||||
|
|
||||||
import { BaseData } from '@shared/models/base-data';
|
import { BaseData, ExportableEntity } from '@shared/models/base-data';
|
||||||
import { DeviceId } from './id/device-id';
|
import { DeviceId } from './id/device-id';
|
||||||
import { TenantId } from '@shared/models/id/tenant-id';
|
import { TenantId } from '@shared/models/id/tenant-id';
|
||||||
import { CustomerId } from '@shared/models/id/customer-id';
|
import { CustomerId } from '@shared/models/id/customer-id';
|
||||||
@ -560,7 +560,7 @@ export interface DeviceProfileData {
|
|||||||
provisionConfiguration?: DeviceProvisionConfiguration;
|
provisionConfiguration?: DeviceProvisionConfiguration;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DeviceProfile extends BaseData<DeviceProfileId> {
|
export interface DeviceProfile extends BaseData<DeviceProfileId>, ExportableEntity<DeviceProfileId> {
|
||||||
tenantId?: TenantId;
|
tenantId?: TenantId;
|
||||||
name: string;
|
name: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
@ -685,7 +685,7 @@ export interface DeviceData {
|
|||||||
transportConfiguration: DeviceTransportConfiguration;
|
transportConfiguration: DeviceTransportConfiguration;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Device extends BaseData<DeviceId> {
|
export interface Device extends BaseData<DeviceId>, ExportableEntity<DeviceId> {
|
||||||
tenantId?: TenantId;
|
tenantId?: TenantId;
|
||||||
customerId?: CustomerId;
|
customerId?: CustomerId;
|
||||||
name: string;
|
name: string;
|
||||||
|
|||||||
@ -14,14 +14,14 @@
|
|||||||
/// limitations under the License.
|
/// limitations under the License.
|
||||||
///
|
///
|
||||||
|
|
||||||
import { BaseData } from '@shared/models/base-data';
|
import { BaseData, ExportableEntity } from '@shared/models/base-data';
|
||||||
import { TenantId } from '@shared/models/id/tenant-id';
|
import { TenantId } from '@shared/models/id/tenant-id';
|
||||||
import { RuleChainId } from '@shared/models/id/rule-chain-id';
|
import { RuleChainId } from '@shared/models/id/rule-chain-id';
|
||||||
import { RuleNodeId } from '@shared/models/id/rule-node-id';
|
import { RuleNodeId } from '@shared/models/id/rule-node-id';
|
||||||
import { RuleNode, RuleNodeComponentDescriptor, RuleNodeType } from '@shared/models/rule-node.models';
|
import { RuleNode, RuleNodeComponentDescriptor, RuleNodeType } from '@shared/models/rule-node.models';
|
||||||
import { ComponentType } from '@shared/models/component-descriptor.models';
|
import { ComponentType } from '@shared/models/component-descriptor.models';
|
||||||
|
|
||||||
export interface RuleChain extends BaseData<RuleChainId> {
|
export interface RuleChain extends BaseData<RuleChainId>, ExportableEntity<RuleChainId> {
|
||||||
tenantId: TenantId;
|
tenantId: TenantId;
|
||||||
name: string;
|
name: string;
|
||||||
firstRuleNodeId: RuleNodeId;
|
firstRuleNodeId: RuleNodeId;
|
||||||
|
|||||||
@ -43,6 +43,7 @@ export interface BranchInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface EntityVersion {
|
export interface EntityVersion {
|
||||||
|
timestamp: number;
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3109,6 +3109,8 @@
|
|||||||
"json-value-required": "JSON value is required."
|
"json-value-required": "JSON value is required."
|
||||||
},
|
},
|
||||||
"version-control": {
|
"version-control": {
|
||||||
|
"version-control": "Version control",
|
||||||
|
"management": "Version control management",
|
||||||
"branch": "Branch",
|
"branch": "Branch",
|
||||||
"select-branch": "Select branch",
|
"select-branch": "Select branch",
|
||||||
"branch-required": "Branch is required",
|
"branch-required": "Branch is required",
|
||||||
@ -3118,7 +3120,13 @@
|
|||||||
"version-name-required": "Version name is required",
|
"version-name-required": "Version name is required",
|
||||||
"export-entity-relations": "Export entity relations",
|
"export-entity-relations": "Export entity relations",
|
||||||
"export-entity-version-result-message": "Entity exported with version '{{name}}' and commit id '{{commitId}}'.",
|
"export-entity-version-result-message": "Entity exported with version '{{name}}' and commit id '{{commitId}}'.",
|
||||||
"export-to-git": "Export to Git"
|
"export-to-git": "Export to Git",
|
||||||
|
"entity-versions": "Entity versions",
|
||||||
|
"versions": "Versions",
|
||||||
|
"created-time": "Created time",
|
||||||
|
"version-id": "Version ID",
|
||||||
|
"no-entity-versions-text": "No entity versions found",
|
||||||
|
"no-versions-text": "No versions found"
|
||||||
},
|
},
|
||||||
"widget": {
|
"widget": {
|
||||||
"widget-library": "Widgets Library",
|
"widget-library": "Widgets Library",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user