Skip to content

Commit 50d48d9

Browse files
authored
Merge pull request ceph#55783 from rhcs-dashboard/multi-cluster-imrovements
mgr/dashboard: Multi cluster improvements Reviewed-by: Nizamudeen A <[email protected]>
2 parents 4a987d7 + 7a08bdd commit 50d48d9

17 files changed

+370
-208
lines changed

src/pybind/mgr/dashboard/controllers/auth.py

Lines changed: 43 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -54,50 +54,6 @@ def create(self, username, password):
5454
pwd_expiration_date = user_data.get('pwdExpirationDate', None)
5555
pwd_update_required = user_data.get('pwdUpdateRequired', False)
5656

57-
if isinstance(Settings.MULTICLUSTER_CONFIG, str):
58-
try:
59-
item_to_dict = json.loads(Settings.MULTICLUSTER_CONFIG)
60-
except json.JSONDecodeError:
61-
item_to_dict = {}
62-
multicluster_config = item_to_dict.copy()
63-
else:
64-
multicluster_config = Settings.MULTICLUSTER_CONFIG.copy()
65-
try:
66-
if fsid in multicluster_config['config']:
67-
existing_entries = multicluster_config['config'][fsid]
68-
if not any(entry['user'] == username for entry in existing_entries):
69-
existing_entries.append({
70-
"name": fsid,
71-
"url": origin,
72-
"cluster_alias": "local-cluster",
73-
"user": username
74-
})
75-
else:
76-
multicluster_config['config'][fsid] = [{
77-
"name": fsid,
78-
"url": origin,
79-
"cluster_alias": "local-cluster",
80-
"user": username
81-
}]
82-
83-
except KeyError:
84-
multicluster_config = {
85-
'current_url': origin,
86-
'current_user': username,
87-
'hub_url': origin,
88-
'config': {
89-
fsid: [
90-
{
91-
"name": fsid,
92-
"url": origin,
93-
"cluster_alias": "local-cluster",
94-
"user": username
95-
}
96-
]
97-
}
98-
}
99-
Settings.MULTICLUSTER_CONFIG = multicluster_config
100-
10157
if user_perms is not None:
10258
url_prefix = 'https' if mgr.get_localized_module_option('ssl') else 'http'
10359

@@ -110,6 +66,49 @@ def create(self, username, password):
11066
token = token.decode('utf-8') if isinstance(token, bytes) else token
11167

11268
self._set_token_cookie(url_prefix, token)
69+
if isinstance(Settings.MULTICLUSTER_CONFIG, str):
70+
try:
71+
item_to_dict = json.loads(Settings.MULTICLUSTER_CONFIG)
72+
except json.JSONDecodeError:
73+
item_to_dict = {}
74+
multicluster_config = item_to_dict.copy()
75+
else:
76+
multicluster_config = Settings.MULTICLUSTER_CONFIG.copy()
77+
try:
78+
if fsid in multicluster_config['config']:
79+
existing_entries = multicluster_config['config'][fsid]
80+
if not any((entry['user'] == username or entry['cluster_alias'] == 'local-cluster') for entry in existing_entries): # noqa E501 #pylint: disable=line-too-long
81+
existing_entries.append({
82+
"name": fsid,
83+
"url": origin,
84+
"cluster_alias": "local-cluster",
85+
"user": username
86+
})
87+
else:
88+
multicluster_config['config'][fsid] = [{
89+
"name": fsid,
90+
"url": origin,
91+
"cluster_alias": "local-cluster",
92+
"user": username
93+
}]
94+
95+
except KeyError:
96+
multicluster_config = {
97+
'current_url': origin,
98+
'current_user': username,
99+
'hub_url': origin,
100+
'config': {
101+
fsid: [
102+
{
103+
"name": fsid,
104+
"url": origin,
105+
"cluster_alias": "local-cluster",
106+
"user": username
107+
}
108+
]
109+
}
110+
}
111+
Settings.MULTICLUSTER_CONFIG = multicluster_config
113112
return {
114113
'token': token,
115114
'username': username,

src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/multi-cluster/multi-cluster-form/multi-cluster-form.component.html

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
<cd-alert-panel type="info"
2121
spacingClass="mb-3"
2222
i18n
23-
*ngIf="connectionVerified !== undefined && !connectionVerified && connectionMessage === 'Connection refused'">
23+
*ngIf="connectionVerified !== undefined && !connectionVerified && connectionMessage === 'Connection refused' || remoteClusterForm.getValue('showToken')">
2424
<p>You need to set this cluster's url as the cross origin url in the remote cluster you are trying to connect.
2525
You can do it by running this CLI command in your remote cluster and proceed with authentication via token.</p>
2626
<cd-code-block [codes]="[crossOriginCmd]"></cd-code-block>
@@ -29,7 +29,14 @@
2929
<label class="cd-col-form-label required"
3030
for="remoteClusterUrl"
3131
i18n>Cluster API URL
32-
<cd-helper>Enter the Dashboard API URL. You can retrieve it from the CLI with: <b>ceph mgr services</b></cd-helper>
32+
<cd-helper>
33+
<span>
34+
<p>Enter the Dashboard API URL. You can retrieve it from the CLI with: <b>{{ clusterApiUrlCmd }} </b>
35+
<cd-copy-2-clipboard-button [source]="clusterApiUrlCmd"
36+
[byId]="false"></cd-copy-2-clipboard-button>
37+
</p>
38+
</span>
39+
</cd-helper>
3340
</label>
3441
<div class="cd-col-form-input">
3542
<input class="form-control"
@@ -110,6 +117,32 @@
110117
</span>
111118
</div>
112119
</div>
120+
<div class="form-group row"
121+
*ngIf="remoteClusterForm.getValue('showToken') && action !== 'edit'">
122+
<label class="cd-col-form-label required"
123+
for="prometheusApiUrl"
124+
i18n>Prometheus API URL
125+
<cd-helper>
126+
<span>
127+
<p>Enter the Prometheus API URL. You can retrieve it from the CLI with: <b>{{ prometheusApiUrlCmd }} </b>
128+
<cd-copy-2-clipboard-button [source]="prometheusApiUrlCmd"
129+
[byId]="false"></cd-copy-2-clipboard-button>
130+
</p>
131+
</span>
132+
</cd-helper>
133+
</label>
134+
<div class="cd-col-form-input">
135+
<input id="prometheusApiUrl"
136+
name="prometheusApiUrl"
137+
class="form-control"
138+
type="text"
139+
formControlName="prometheusApiUrl">
140+
<span class="invalid-feedback"
141+
*ngIf="remoteClusterForm.showError('prometheusApiUrl', frm, 'required')"
142+
i18n>This field is required.
143+
</span>
144+
</div>
145+
</div>
113146
<div class="form-group row"
114147
*ngIf="!remoteClusterForm.getValue('showToken') && !showCrossOriginError && action !== 'edit'">
115148
<label class="cd-col-form-label required"
@@ -211,7 +244,7 @@
211244
</div>
212245
</div>
213246
<div class="form-group row"
214-
*ngIf="!showCrossOriginError && action !== 'edit' && !remoteClusterForm.getValue('showToken')">
247+
*ngIf="!showCrossOriginError && action !== 'edit' && !remoteClusterForm.getValue('showToken') && !connectionVerified">
215248
<div class="cd-col-form-offset">
216249
<div class="custom-control">
217250
<button class="btn btn-primary"

src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/multi-cluster/multi-cluster-form/multi-cluster-form.component.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,15 @@ export class MultiClusterFormComponent implements OnInit, OnDestroy {
2222
readonly endpoints = /^((https?:\/\/)|(www.))(?:([a-zA-Z]+)|(\d+\.\d+.\d+.\d+)):\d{2,5}\/?$/;
2323
readonly ipv4Rgx = /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/i;
2424
readonly ipv6Rgx = /^(?:[a-f0-9]{1,4}:){7}[a-f0-9]{1,4}$/i;
25+
clusterApiUrlCmd = 'ceph mgr services';
26+
prometheusApiUrlCmd = 'ceph config get mgr mgr/dashboard/PROMETHEUS_API_HOST';
27+
crossOriginCmd = `ceph dashboard set-cross-origin-url ${window.location.origin}`;
2528
remoteClusterForm: CdFormGroup;
2629
showToken = false;
2730
connectionVerified: boolean;
2831
connectionMessage = '';
2932
private subs = new Subscription();
3033
showCrossOriginError = false;
31-
crossOriginCmd: string;
3234
action: string;
3335
cluster: MultiCluster;
3436
clustersData: MultiCluster[];
@@ -99,6 +101,11 @@ export class MultiClusterFormComponent implements OnInit, OnDestroy {
99101
showToken: true
100102
})
101103
]),
104+
prometheusApiUrl: new FormControl('', [
105+
CdValidators.requiredIf({
106+
showToken: true
107+
})
108+
]),
102109
password: new FormControl('', []),
103110
remoteClusterUrl: new FormControl(null, {
104111
validators: [
@@ -152,6 +159,7 @@ export class MultiClusterFormComponent implements OnInit, OnDestroy {
152159
const url = this.remoteClusterForm.getValue('remoteClusterUrl');
153160
const updatedUrl = url.endsWith('/') ? url.slice(0, -1) : url;
154161
const clusterAlias = this.remoteClusterForm.getValue('clusterAlias');
162+
const prometheusApiUrl = this.remoteClusterForm.getValue('prometheusApiUrl');
155163
const username = this.remoteClusterForm.getValue('username');
156164
const password = this.remoteClusterForm.getValue('password');
157165
const token = this.remoteClusterForm.getValue('apiToken');
@@ -210,6 +218,7 @@ export class MultiClusterFormComponent implements OnInit, OnDestroy {
210218
token,
211219
window.location.origin,
212220
clusterFsid,
221+
prometheusApiUrl,
213222
ssl,
214223
ssl_certificate
215224
)
@@ -256,7 +265,6 @@ export class MultiClusterFormComponent implements OnInit, OnDestroy {
256265
this.connectionVerified = false;
257266
this.showCrossOriginError = true;
258267
this.connectionMessage = resp;
259-
this.crossOriginCmd = `ceph config set mgr mgr/dashboard/cross_origin_url ${window.location.origin} `;
260268
this.notificationService.show(
261269
NotificationType.error,
262270
$localize`Connection to the cluster failed`

src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/multi-cluster/multi-cluster-list/multi-cluster-list.component.ts

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import { NotificationService } from '~/app/shared/services/notification.service'
1616
import { NotificationType } from '~/app/shared/enum/notification-type.enum';
1717
import { CellTemplate } from '~/app/shared/enum/cell-template.enum';
1818
import { MultiCluster } from '~/app/shared/models/multi-cluster';
19-
import { SummaryService } from '~/app/shared/services/summary.service';
2019
import { Router } from '@angular/router';
2120
import { CookiesService } from '~/app/shared/services/cookie.service';
2221

@@ -41,11 +40,12 @@ export class MultiClusterListComponent {
4140
clustersTokenMap: Map<string, string> = new Map<string, string>();
4241
newData: any;
4342
modalRef: NgbModalRef;
43+
hubUrl: string;
44+
currentUrl: string;
4445

4546
constructor(
4647
private multiClusterService: MultiClusterService,
4748
private router: Router,
48-
private summaryService: SummaryService,
4949
public actionLabels: ActionLabelsI18n,
5050
private notificationService: NotificationService,
5151
private authStorageService: AuthStorageService,
@@ -57,6 +57,7 @@ export class MultiClusterListComponent {
5757
permission: 'create',
5858
icon: Icons.add,
5959
name: this.actionLabels.CONNECT,
60+
disable: (selection: CdTableSelection) => this.getDisable('connect', selection),
6061
click: () => this.openRemoteClusterInfoModal('connect')
6162
},
6263
{
@@ -87,6 +88,8 @@ export class MultiClusterListComponent {
8788
ngOnInit(): void {
8889
this.multiClusterService.subscribe((resp: object) => {
8990
if (resp && resp['config']) {
91+
this.hubUrl = resp['hub_url'];
92+
this.currentUrl = resp['current_url'];
9093
const clusterDetailsArray = Object.values(resp['config']).flat();
9194
this.data = clusterDetailsArray;
9295
this.checkClusterConnectionStatus();
@@ -139,7 +142,7 @@ export class MultiClusterListComponent {
139142
checkClusterConnectionStatus() {
140143
if (this.clusterTokenStatus && this.data) {
141144
this.data.forEach((cluster: MultiCluster) => {
142-
const clusterStatus = this.clusterTokenStatus[cluster.name];
145+
const clusterStatus = this.clusterTokenStatus[cluster.name.trim()];
143146

144147
if (clusterStatus !== undefined) {
145148
cluster.cluster_connection_status = clusterStatus.status;
@@ -164,18 +167,10 @@ export class MultiClusterListComponent {
164167
size: 'xl'
165168
});
166169
this.bsModalRef.componentInstance.submitAction.subscribe(() => {
167-
this.multiClusterService.refresh();
168-
this.summaryService.refresh();
169170
const currentRoute = this.router.url.split('?')[0];
170-
if (currentRoute.includes('dashboard')) {
171-
this.router.navigateByUrl('/pool', { skipLocationChange: true }).then(() => {
172-
this.router.navigate([currentRoute]);
173-
});
174-
} else {
175-
this.router.navigateByUrl('/', { skipLocationChange: true }).then(() => {
176-
this.router.navigate([currentRoute]);
177-
});
178-
}
171+
this.multiClusterService.refreshMultiCluster(currentRoute);
172+
this.checkClusterConnectionStatus();
173+
this.multiClusterService.isClusterAdded(true);
179174
});
180175
}
181176

@@ -186,28 +181,35 @@ export class MultiClusterListComponent {
186181
openDeleteClusterModal() {
187182
const cluster = this.selection.first();
188183
this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, {
184+
infoMessage: $localize`Please note that the data for the disconnected cluster will be visible for a duration of ~ 5 minutes. After this period, it will be automatically removed.`,
189185
actionDescription: $localize`Disconnect`,
190186
itemDescription: $localize`Cluster`,
191187
itemNames: [cluster['cluster_alias'] + ' - ' + cluster['user']],
192188
submitAction: () =>
193189
this.multiClusterService.deleteCluster(cluster['name'], cluster['user']).subscribe(() => {
194190
this.cookieService.deleteToken(`${cluster['name']}-${cluster['user']}`);
191+
this.multiClusterService.showPrometheusDelayMessage(true);
195192
this.modalRef.close();
196193
this.notificationService.show(
197194
NotificationType.success,
198195
$localize`Disconnected cluster '${cluster['cluster_alias']}'`
199196
);
197+
const currentRoute = this.router.url.split('?')[0];
198+
this.multiClusterService.refreshMultiCluster(currentRoute);
200199
})
201200
});
202201
}
203202

204203
getDisable(action: string, selection: CdTableSelection): string | boolean {
205-
if (!selection.hasSelection) {
204+
if (this.hubUrl !== this.currentUrl) {
205+
return $localize`Please switch to the local-cluster to ${action} a remote cluster`;
206+
}
207+
if (!selection.hasSelection && action !== 'connect') {
206208
return $localize`Please select one or more clusters to ${action}`;
207209
}
208210
if (selection.hasSingleSelection) {
209211
const cluster = selection.first();
210-
if (cluster['cluster_alias'] === 'local-cluster') {
212+
if (cluster['cluster_alias'] === 'local-cluster' && action !== 'connect') {
211213
return $localize`Cannot ${action} local cluster`;
212214
}
213215
}

0 commit comments

Comments
 (0)