mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-24 17:47:46 +01:00
commit
768f20164b
@ -65,10 +65,13 @@ export class PasswordSettingComponent implements AfterViewChecked {
|
||||
let cont = this.pwdForm.controls[key];
|
||||
if (cont) {
|
||||
this.validationStateMap[key] = cont.valid;
|
||||
if (key === "reNewPassword" && cont.valid) {
|
||||
let compareCont = this.pwdForm.controls["newPassword"];
|
||||
if (compareCont) {
|
||||
this.validationStateMap[key] = cont.value === compareCont.value;
|
||||
if (cont.valid) {
|
||||
if (key === "reNewPassword" || key === "newPassword") {
|
||||
let cpKey = key === "reNewPassword" ? "newPassword" : "reNewPassword";
|
||||
let compareCont = this.pwdForm.controls[cpKey];
|
||||
if (compareCont && compareCont.valid) {
|
||||
this.validationStateMap["reNewPassword"] = cont.value === compareCont.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -74,7 +74,7 @@ export class ResetPasswordComponent implements OnInit {
|
||||
}
|
||||
|
||||
public close(): void {
|
||||
//If already reset password ok, navigator to sign-in
|
||||
//If already reset password ok, navigator to sign-in
|
||||
if (this.resetOk) {
|
||||
this.router.navigateByUrl(CommonRoutes.EMBEDDED_SIGN_IN);
|
||||
}
|
||||
@ -114,7 +114,10 @@ export class ResetPasswordComponent implements OnInit {
|
||||
this.validationState[key] = true;
|
||||
}
|
||||
} else {
|
||||
this.validationState[key] = this.getControlValidationState(key)
|
||||
this.validationState[key] = this.getControlValidationState(key);
|
||||
if (this.validationState[key]) {
|
||||
this.validationState["reNewPassword"] = this.samePassword();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -38,6 +38,6 @@
|
||||
|
||||
.more-info-link {
|
||||
position: relative;
|
||||
top: 100px;
|
||||
top: 90px;
|
||||
left: 330px;
|
||||
}
|
@ -9,6 +9,7 @@
|
||||
.start-content-padding {
|
||||
padding: 0px !important;
|
||||
background-color: white;
|
||||
overflow-y: hidden !important;
|
||||
}
|
||||
|
||||
.content-area-override {
|
||||
|
@ -27,7 +27,7 @@ export class NavigatorComponent implements OnInit {
|
||||
|
||||
private selectedLang: string = enLang;
|
||||
private appTitle: string = 'APP_TITLE.HARBOR';
|
||||
|
||||
|
||||
constructor(
|
||||
private session: SessionService,
|
||||
private router: Router,
|
||||
@ -36,8 +36,8 @@ export class NavigatorComponent implements OnInit {
|
||||
private appConfigService: AppConfigService,
|
||||
private msgHandler: MessageHandlerService,
|
||||
private searchTrigger: SearchTriggerService) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.selectedLang = this.translate.currentLang;
|
||||
@ -80,8 +80,8 @@ export class NavigatorComponent implements OnInit {
|
||||
|
||||
public get canChangePassword(): boolean {
|
||||
return this.session.getCurrentUser() &&
|
||||
this.appConfigService.getConfig() &&
|
||||
this.appConfigService.getConfig().auth_mode != 'ldap_auth';
|
||||
this.appConfigService.getConfig() &&
|
||||
this.appConfigService.getConfig().auth_mode != 'ldap_auth';
|
||||
}
|
||||
|
||||
matchLang(lang: string): boolean {
|
||||
@ -114,16 +114,14 @@ export class NavigatorComponent implements OnInit {
|
||||
|
||||
//Log out system
|
||||
logOut(): void {
|
||||
this.session.signOff()
|
||||
.then(() => {
|
||||
//Naviagte to the sign in route
|
||||
this.router.navigate([CommonRoutes.EMBEDDED_SIGN_IN]);
|
||||
})
|
||||
.catch(error => {
|
||||
this.msgHandler.handleError(error);
|
||||
});
|
||||
//Naviagte to the sign in route
|
||||
//Appending 'signout' means destroy session cache
|
||||
let navigatorExtra: NavigationExtras = {
|
||||
queryParams: { "signout": true }
|
||||
};
|
||||
this.router.navigate([CommonRoutes.EMBEDDED_SIGN_IN], navigatorExtra);
|
||||
//Confirm search result panel is close
|
||||
this.searchTrigger.closeSearch(true);
|
||||
this.searchTrigger.closeSearch(true);
|
||||
}
|
||||
|
||||
//Switch languages
|
||||
@ -135,7 +133,7 @@ export class NavigatorComponent implements OnInit {
|
||||
//TODO:
|
||||
console.error('Language ' + lang.trim() + ' is not suppoted');
|
||||
}
|
||||
setTimeout(()=>{
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 500);
|
||||
}
|
||||
|
@ -1,8 +1,10 @@
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<h2 class="header-title">{{'PROJECT.PROJECTS' | translate}}</h2>
|
||||
<div>
|
||||
<statistics-panel></statistics-panel>
|
||||
<div class="row flex-items-xs-between flex-items-xs-top">
|
||||
<h2 class="header-title">{{'PROJECT.PROJECTS' | translate}}</h2>
|
||||
<div>
|
||||
<statistics-panel></statistics-panel>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row flex-items-xs-between">
|
||||
<div class="option-left">
|
||||
|
@ -25,6 +25,7 @@ import { State } from 'clarity-angular';
|
||||
import { AppConfigService } from '../app-config.service';
|
||||
import { SessionService } from '../shared/session.service';
|
||||
import { ProjectTypes } from '../shared/shared.const';
|
||||
import { StatisticHandler } from '../shared/statictics/statistic-handler.service';
|
||||
|
||||
@Component({
|
||||
selector: 'project',
|
||||
@ -61,7 +62,8 @@ export class ProjectComponent implements OnInit, OnDestroy {
|
||||
private messageHandlerService: MessageHandlerService,
|
||||
private appConfigService: AppConfigService,
|
||||
private sessionService: SessionService,
|
||||
private deletionDialogService: ConfirmationDialogService) {
|
||||
private deletionDialogService: ConfirmationDialogService,
|
||||
private statisticHandler: StatisticHandler) {
|
||||
this.subscription = deletionDialogService.confirmationConfirm$.subscribe(message => {
|
||||
if (message &&
|
||||
message.state === ConfirmationState.CONFIRMED &&
|
||||
@ -72,8 +74,8 @@ export class ProjectComponent implements OnInit, OnDestroy {
|
||||
.subscribe(
|
||||
response => {
|
||||
this.messageHandlerService.showSuccess('PROJECT.DELETED_SUCCESS');
|
||||
console.log('Successful delete project with ID:' + projectId);
|
||||
this.retrieve();
|
||||
this.statisticHandler.refresh();
|
||||
},
|
||||
error =>{
|
||||
if(error && error.status === 412) {
|
||||
@ -123,7 +125,6 @@ export class ProjectComponent implements OnInit, OnDestroy {
|
||||
response => {
|
||||
this.totalRecordCount = response.headers.get('x-total-count');
|
||||
this.totalPage = Math.ceil(this.totalRecordCount / this.pageSize);
|
||||
console.log('TotalRecordCount:' + this.totalRecordCount + ', totalPage:' + this.totalPage);
|
||||
this.changedProjects = response.json();
|
||||
},
|
||||
error => this.messageHandlerService.handleError(error)
|
||||
@ -138,17 +139,16 @@ export class ProjectComponent implements OnInit, OnDestroy {
|
||||
if (created) {
|
||||
this.projectName = '';
|
||||
this.retrieve();
|
||||
this.statisticHandler.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
doSearchProjects(projectName: string): void {
|
||||
console.log('Search for project name:' + projectName);
|
||||
this.projectName = projectName;
|
||||
this.retrieve();
|
||||
}
|
||||
|
||||
doFilterProjects(filteredType: number): void {
|
||||
console.log('Filter projects with type:' + this.projectTypes[filteredType]);
|
||||
this.isPublic = filteredType;
|
||||
this.currentFilteredType = filteredType;
|
||||
this.retrieve();
|
||||
@ -162,7 +162,7 @@ export class ProjectComponent implements OnInit, OnDestroy {
|
||||
.subscribe(
|
||||
response => {
|
||||
this.messageHandlerService.showSuccess('PROJECT.TOGGLED_SUCCESS');
|
||||
console.log('Successful toggled project_id:' + p.project_id);
|
||||
this.statisticHandler.refresh();
|
||||
},
|
||||
error => this.messageHandlerService.handleError(error)
|
||||
);
|
||||
@ -182,6 +182,7 @@ export class ProjectComponent implements OnInit, OnDestroy {
|
||||
|
||||
refresh(): void {
|
||||
this.retrieve();
|
||||
this.statisticHandler.refresh();
|
||||
}
|
||||
|
||||
}
|
@ -42,7 +42,6 @@ export class DestinationComponent implements OnInit {
|
||||
.subscribe(
|
||||
response => {
|
||||
this.messageHandlerService.showSuccess('DESTINATION.DELETED_SUCCESS');
|
||||
console.log('Successful deleted target with ID:' + targetId);
|
||||
this.reload();
|
||||
},
|
||||
error => {
|
||||
@ -51,7 +50,6 @@ export class DestinationComponent implements OnInit {
|
||||
} else {
|
||||
this.messageHandlerService.handleError(error);
|
||||
}
|
||||
console.log('Failed to delete target with ID:' + targetId + ', error:' + error);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -90,7 +90,6 @@ export class ReplicationComponent implements OnInit {
|
||||
|
||||
ngOnInit(): void {
|
||||
this.projectId = +this.route.snapshot.parent.params['id'];
|
||||
console.log('Get projectId from route params snapshot:' + this.projectId);
|
||||
this.search = new SearchOption();
|
||||
this.currentRuleStatus = this.ruleStatus[0];
|
||||
this.currentJobStatus = this.jobStatus[0];
|
||||
@ -125,13 +124,11 @@ export class ReplicationComponent implements OnInit {
|
||||
}
|
||||
|
||||
openModal(): void {
|
||||
console.log('Open modal to create policy.');
|
||||
this.createEditPolicyComponent.openCreateEditPolicy(true);
|
||||
}
|
||||
|
||||
openEditPolicy(policy: Policy) {
|
||||
if(policy) {
|
||||
console.log('Open modal to edit policy ID:' + policy.id);
|
||||
let editable = true;
|
||||
if(policy.enabled === 1) {
|
||||
editable = false;
|
||||
|
@ -19,7 +19,6 @@ export class ReplicationService {
|
||||
if(!projectId) {
|
||||
projectId = '';
|
||||
}
|
||||
console.log('Get policies with project ID:' + projectId + ', policy name:' + policyName);
|
||||
return this.http
|
||||
.get(`/api/policies/replication?project_id=${projectId}&name=${policyName}`)
|
||||
.map(response=>response.json() as Policy[])
|
||||
@ -27,7 +26,6 @@ export class ReplicationService {
|
||||
}
|
||||
|
||||
getPolicy(policyId: number): Observable<Policy> {
|
||||
console.log('Get policy with ID:' + policyId);
|
||||
return this.http
|
||||
.get(`/api/policies/replication/${policyId}`)
|
||||
.map(response=>response.json() as Policy)
|
||||
@ -35,7 +33,6 @@ export class ReplicationService {
|
||||
}
|
||||
|
||||
createPolicy(policy: Policy): Observable<any> {
|
||||
console.log('Create policy with project ID:' + policy.project_id + ', policy:' + JSON.stringify(policy));
|
||||
return this.http
|
||||
.post(`/api/policies/replication`, JSON.stringify(policy))
|
||||
.map(response=>response.status)
|
||||
@ -107,7 +104,6 @@ export class ReplicationService {
|
||||
|
||||
// /api/jobs/replication/?page=1&page_size=20&end_time=&policy_id=1&start_time=&status=&repository=
|
||||
listJobs(policyId: number, status: string = '', repoName: string = '', startTime: string = '', endTime: string = '', page: number, pageSize: number): Observable<any> {
|
||||
console.log('Get jobs under policy ID:' + policyId);
|
||||
return this.http
|
||||
.get(`/api/jobs/replication?policy_id=${policyId}&status=${status}&repository=${repoName}&start_time=${startTime}&end_time=${endTime}&page=${page}&page_size=${pageSize}`)
|
||||
.map(response=>response)
|
||||
@ -115,7 +111,6 @@ export class ReplicationService {
|
||||
}
|
||||
|
||||
listTargets(targetName: string): Observable<Target[]> {
|
||||
console.log('Get targets.');
|
||||
return this.http
|
||||
.get(`/api/targets?name=${targetName}`)
|
||||
.map(response=>response.json() as Target[])
|
||||
@ -123,7 +118,6 @@ export class ReplicationService {
|
||||
}
|
||||
|
||||
listTargetPolicies(targetId: number): Observable<Policy[]> {
|
||||
console.log('List target with policy.');
|
||||
return this.http
|
||||
.get(`/api/targets/${targetId}/policies`)
|
||||
.map(response=>response.json() as Policy[])
|
||||
@ -131,7 +125,6 @@ export class ReplicationService {
|
||||
}
|
||||
|
||||
getTarget(targetId: number): Observable<Target> {
|
||||
console.log('Get target by ID:' + targetId);
|
||||
return this.http
|
||||
.get(`/api/targets/${targetId}`)
|
||||
.map(response=>response.json() as Target)
|
||||
@ -139,7 +132,6 @@ export class ReplicationService {
|
||||
}
|
||||
|
||||
createTarget(target: Target): Observable<any> {
|
||||
console.log('Create target:' + JSON.stringify(target));
|
||||
return this.http
|
||||
.post(`/api/targets`, JSON.stringify(target))
|
||||
.map(response=>response.status)
|
||||
@ -147,7 +139,6 @@ export class ReplicationService {
|
||||
}
|
||||
|
||||
pingTarget(target: Target): Observable<any> {
|
||||
console.log('Ping target.');
|
||||
let body = new URLSearchParams();
|
||||
body.set('endpoint', target.endpoint);
|
||||
body.set('username', target.username);
|
||||
@ -159,7 +150,6 @@ export class ReplicationService {
|
||||
}
|
||||
|
||||
updateTarget(target: Target): Observable<any> {
|
||||
console.log('Update target with target ID' + target.id);
|
||||
return this.http
|
||||
.put(`/api/targets/${target.id}`, JSON.stringify(target))
|
||||
.map(response=>response.status)
|
||||
@ -167,7 +157,6 @@ export class ReplicationService {
|
||||
}
|
||||
|
||||
deleteTarget(targetId: number): Observable<any> {
|
||||
console.log('Deleting target with ID:' + targetId);
|
||||
return this.http
|
||||
.delete(`/api/targets/${targetId}`)
|
||||
.map(response=>response.status)
|
||||
|
@ -50,7 +50,6 @@ export class TotalReplicationComponent implements OnInit {
|
||||
|
||||
openEditPolicy(policy: Policy) {
|
||||
if(policy) {
|
||||
console.log('Open modal to edit policy ID:' + policy.id);
|
||||
let editable = true;
|
||||
if(policy.enabled === 1) {
|
||||
editable = false;
|
||||
|
@ -154,7 +154,6 @@ export class GaugeComponent implements AfterViewInit {
|
||||
@ViewChild('barTwo') private barTwo: ElementRef;
|
||||
|
||||
private determineColors() {
|
||||
console.info(this._used, this._threasHold);
|
||||
let percent: number = 0;
|
||||
if (this._threasHold !== 0) {
|
||||
percent = (this._used / this._threasHold) * 100;
|
||||
|
@ -142,10 +142,11 @@ export class NewUserFormComponent implements AfterViewChecked, OnInit {
|
||||
}
|
||||
|
||||
//Check password confirmation
|
||||
if (key === "confirmPassword") {
|
||||
let peerCont = this.newUserForm.controls["newPassword"];
|
||||
if (peerCont) {
|
||||
this.validationStateMap[key] = cont.value === peerCont.value;
|
||||
if (key === "confirmPassword" || key === "newPassword") {
|
||||
let cpKey = key === "confirmPassword" ? "newPassword" : "confirmPassword";
|
||||
let peerCont = this.newUserForm.controls[cpKey];
|
||||
if (peerCont && peerCont.valid) {
|
||||
this.validationStateMap["confirmPassword"] = cont.value === peerCont.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,7 +36,6 @@ export class AuthCheckGuard implements CanActivate, CanActivateChild {
|
||||
//When routing change, clear
|
||||
this.msgHandler.clear();
|
||||
this.searchTrigger.closeSearch(true);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
//Before activating, we firstly need to confirm whether the route is coming from peer part - admiral
|
||||
let queryParams = route.queryParams;
|
||||
|
@ -15,19 +15,33 @@ export class SignInGuard implements CanActivate, CanActivateChild {
|
||||
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> | boolean {
|
||||
//If user has logged in, should not login again
|
||||
return new Promise((resolve, reject) => {
|
||||
let user = this.authService.getCurrentUser();
|
||||
if (user === null) {
|
||||
this.authService.retrieveUser()
|
||||
//If signout appended
|
||||
let queryParams = route.queryParams;
|
||||
if (queryParams && queryParams['signout']) {
|
||||
this.authService.signOff()
|
||||
.then(() => {
|
||||
this.router.navigate([CommonRoutes.HARBOR_DEFAULT]);
|
||||
return resolve(false);
|
||||
this.authService.clear();//Destroy session cache
|
||||
return resolve(true);
|
||||
})
|
||||
.catch(error => {
|
||||
return resolve(true);
|
||||
console.error(error);
|
||||
return resolve(false);
|
||||
});
|
||||
} else {
|
||||
this.router.navigate([CommonRoutes.HARBOR_DEFAULT]);
|
||||
return resolve(false);
|
||||
let user = this.authService.getCurrentUser();
|
||||
if (user === null) {
|
||||
this.authService.retrieveUser()
|
||||
.then(() => {
|
||||
this.router.navigate([CommonRoutes.HARBOR_DEFAULT]);
|
||||
return resolve(false);
|
||||
})
|
||||
.catch(error => {
|
||||
return resolve(true);
|
||||
});
|
||||
} else {
|
||||
this.router.navigate([CommonRoutes.HARBOR_DEFAULT]);
|
||||
return resolve(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -93,7 +93,7 @@ export class SessionService {
|
||||
return this.http.get(signOffEndpoint, { headers: this.headers }).toPromise()
|
||||
.then(() => {
|
||||
//Destroy current session cache
|
||||
this.currentUser = null;
|
||||
//this.currentUser = null;
|
||||
}) //Nothing returned
|
||||
.catch(error => this.handleError(error))
|
||||
}
|
||||
|
@ -40,6 +40,7 @@ import { ListRepositoryROComponent } from './list-repository-ro/list-repository-
|
||||
import { MessageHandlerService } from './message-handler/message-handler.service';
|
||||
import { EmailValidatorDirective } from './email.directive';
|
||||
import { GaugeComponent } from './gauge/gauge.component';
|
||||
import { StatisticHandler } from './statictics/statistic-handler.service';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@ -97,7 +98,8 @@ import { GaugeComponent } from './gauge/gauge.component';
|
||||
SignInGuard,
|
||||
LeavingConfigRouteDeactivate,
|
||||
MemberGuard,
|
||||
MessageHandlerService
|
||||
MessageHandlerService,
|
||||
StatisticHandler
|
||||
]
|
||||
})
|
||||
export class SharedModule {
|
||||
|
@ -0,0 +1,14 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Subject } from 'rxjs/Subject';
|
||||
|
||||
@Injectable()
|
||||
export class StatisticHandler {
|
||||
|
||||
private refreshSource = new Subject<boolean>();
|
||||
|
||||
refreshChan$ = this.refreshSource.asObservable();
|
||||
|
||||
refresh() {
|
||||
this.refreshSource.next(true);
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { Component, Input, OnInit, OnDestroy } from '@angular/core';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
|
||||
import { StatisticsService } from './statistics.service';
|
||||
import { Statistics } from './statistics';
|
||||
@ -7,6 +8,7 @@ import { SessionService } from '../session.service';
|
||||
import { Volumes } from './volumes';
|
||||
|
||||
import { MessageHandlerService } from '../message-handler/message-handler.service';
|
||||
import { StatisticHandler } from './statistic-handler.service';
|
||||
|
||||
@Component({
|
||||
selector: 'statistics-panel',
|
||||
@ -15,26 +17,40 @@ import { MessageHandlerService } from '../message-handler/message-handler.servic
|
||||
providers: [StatisticsService]
|
||||
})
|
||||
|
||||
export class StatisticsPanelComponent implements OnInit {
|
||||
export class StatisticsPanelComponent implements OnInit, OnDestroy {
|
||||
|
||||
private originalCopy: Statistics = new Statistics();
|
||||
private volumesInfo: Volumes = new Volumes();
|
||||
refreshSub: Subscription;
|
||||
|
||||
constructor(
|
||||
private statistics: StatisticsService,
|
||||
private msgHandler: MessageHandlerService,
|
||||
private session: SessionService) { }
|
||||
private session: SessionService,
|
||||
private statisticHandler: StatisticHandler) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
//Refresh
|
||||
this.refreshSub = this.statisticHandler.refreshChan$.subscribe(clear => {
|
||||
this.getStatistics();
|
||||
});
|
||||
|
||||
if (this.session.getCurrentUser()) {
|
||||
this.getStatistics();
|
||||
}
|
||||
|
||||
|
||||
if (this.isValidSession) {
|
||||
this.getVolumes();
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (this.refreshSub) {
|
||||
this.refreshSub.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
public get totalStorage(): number {
|
||||
return this.getGBFromBytes(this.volumesInfo.storage.total);
|
||||
}
|
||||
|
@ -10,7 +10,7 @@
|
||||
.statistic-data {
|
||||
font-size: 16px;
|
||||
font-weight: 900;
|
||||
font-family: "Metropolis Semibold";
|
||||
font-family: 'Metropolis,"Avenir Next","Helvetica Neue",Arial,sans-serif';
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
@ -18,7 +18,7 @@
|
||||
font-size: 10px;
|
||||
line-height: 10px;
|
||||
text-transform: uppercase;
|
||||
font-family: "Metropolis Regular";
|
||||
font-family: 'Metropolis,"Avenir Next","Helvetica Neue",Arial,sans-serif';
|
||||
}
|
||||
|
||||
.statistic-column-block {
|
||||
@ -30,7 +30,7 @@
|
||||
position: relative;
|
||||
text-transform: uppercase;
|
||||
font-size: 14px;
|
||||
font-family: "Metropolis Regular";
|
||||
font-family: 'Metropolis,"Avenir Next","Helvetica Neue",Arial,sans-serif';
|
||||
}
|
||||
|
||||
.statistic-column-title-pro {
|
||||
|
@ -10,7 +10,11 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<harbor-app>Loading...</harbor-app>
|
||||
<harbor-app>
|
||||
<div class="spinner spinner-lg app-loading">
|
||||
Loading...
|
||||
</div>
|
||||
</harbor-app>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -0,0 +1,9 @@
|
||||
.app-loading {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin-top: -54px;
|
||||
margin-left: -54px;
|
||||
width: 108px !important;
|
||||
height: 108px !important;
|
||||
}
|
Loading…
Reference in New Issue
Block a user