Skip to content
GitLab
Projects
Groups
Snippets
Help
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Open sidebar
CSAN
Csan
Commits
a13642c2
Commit
a13642c2
authored
Jul 01, 2020
by
AllForNothing
Browse files
Add proxy cache ui
Signed-off-by:
AllForNothing
<
sshijun@vmware.com
>
parent
468ba50a
Changes
23
Hide whitespace changes
Inline
Side-by-side
Showing
20 changed files
with
306 additions
and
31 deletions
+306
-31
src/portal/src/app/project/create-project/create-project.component.html
.../app/project/create-project/create-project.component.html
+40
-1
src/portal/src/app/project/create-project/create-project.component.spec.ts
...p/project/create-project/create-project.component.spec.ts
+19
-1
src/portal/src/app/project/create-project/create-project.component.ts
...rc/app/project/create-project/create-project.component.ts
+42
-6
src/portal/src/app/project/create-project/create-project.scss
...portal/src/app/project/create-project/create-project.scss
+35
-1
src/portal/src/app/project/helm-chart/helm-chart-detail/chart-detail/chart-detail.component.spec.ts
...-chart-detail/chart-detail/chart-detail.component.spec.ts
+1
-0
src/portal/src/app/project/list-project/list-project.component.html
.../src/app/project/list-project/list-project.component.html
+2
-0
src/portal/src/app/project/list-project/list-project.component.ts
...al/src/app/project/list-project/list-project.component.ts
+5
-0
src/portal/src/app/project/project-detail/project-detail.component.html
.../app/project/project-detail/project-detail.component.html
+8
-2
src/portal/src/app/project/project-detail/project-detail.component.scss
.../app/project/project-detail/project-detail.component.scss
+12
-0
src/portal/src/app/project/project-detail/project-detail.component.ts
...rc/app/project/project-detail/project-detail.component.ts
+4
-0
src/portal/src/app/project/project.ts
src/portal/src/app/project/project.ts
+1
-0
src/portal/src/app/project/summary/summary.component.html
src/portal/src/app/project/summary/summary.component.html
+11
-5
src/portal/src/app/project/summary/summary.component.scss
src/portal/src/app/project/summary/summary.component.scss
+4
-0
src/portal/src/app/project/summary/summary.component.spec.ts
src/portal/src/app/project/summary/summary.component.spec.ts
+55
-4
src/portal/src/app/project/summary/summary.component.ts
src/portal/src/app/project/summary/summary.component.ts
+36
-5
src/portal/src/i18n/lang/en-us-lang.json
src/portal/src/i18n/lang/en-us-lang.json
+7
-2
src/portal/src/i18n/lang/es-es-lang.json
src/portal/src/i18n/lang/es-es-lang.json
+6
-1
src/portal/src/i18n/lang/fr-fr-lang.json
src/portal/src/i18n/lang/fr-fr-lang.json
+6
-1
src/portal/src/i18n/lang/pt-br-lang.json
src/portal/src/i18n/lang/pt-br-lang.json
+6
-1
src/portal/src/i18n/lang/tr-tr-lang.json
src/portal/src/i18n/lang/tr-tr-lang.json
+6
-1
No files found.
src/portal/src/app/project/create-project/create-project.component.html
View file @
a13642c2
...
...
@@ -41,7 +41,7 @@
</clr-tooltip>
</label>
<div
class=
"clr-control-container"
[class.clr-error]=
"(projectStorageLimit.invalid && (projectStorageLimit.dirty || projectStorageLimit.touched))||projectStorageLimit.errors"
>
<input
type=
"text"
id=
"create_project_storage_limit"
[(ngModel)]=
"storageLimit"
name=
"create_project_storage_limit"
class=
"mr-10 clr-input"
<input
type=
"text"
id=
"create_project_storage_limit"
[(ngModel)]=
"storageLimit"
name=
"create_project_storage_limit"
class=
"mr-10 clr-input
width-182
"
#projectStorageLimit
="
ngModel
"
autocomplete=
"off"
>
<clr-icon
class=
"clr-validate-icon"
shape=
"exclamation-circle"
></clr-icon>
<div
class=
"clr-select-wrapper"
>
...
...
@@ -58,6 +58,45 @@
</clr-control-error>
</div>
</div>
<div
class=
"clr-form-control"
*ngIf=
"isSystemAdmin"
>
<label
for=
"create_project_storage_limit"
class=
"clr-control-label"
>
{{ 'PROJECT.PROXY_CACHE' | translate }}
<clr-tooltip>
<clr-icon
clrTooltipTrigger
shape=
"info-circle"
size=
"24"
></clr-icon>
<clr-tooltip-content
clrPosition=
"bottom-left"
clrSize=
"lg"
*clrIfOpen
>
<span>
{{ 'PROJECT.PROXY_CACHE_TOOLTIP' | translate }}
</span>
</clr-tooltip-content>
</clr-tooltip>
</label>
<clr-toggle-wrapper
class=
"mt-02"
>
<input
type=
"checkbox"
clrToggle
name=
"proxy-cache"
id=
"proxy-cache"
[(ngModel)]=
"enableProxyCache"
/>
</clr-toggle-wrapper>
<div
*ngIf=
"enableProxyCache"
class=
"clr-select-wrapper ml-1"
>
<select
class=
"width-164"
id=
"registry"
name=
"registry"
[(ngModel)]=
"project.registry_id"
>
<option
class=
"display-none"
value=
""
></option>
<option
*ngFor=
"let r of registries"
[value]=
"r.id"
>
{{r.name}}-{{r.url}}
</option>
</select>
</div>
</div>
<div
class=
"clr-form-control mt-0"
*ngIf=
"isSystemAdmin && enableProxyCache"
>
<label
for=
"create_project_storage_limit"
class=
"clr-control-label"
></label>
<div
class=
"clr-control-container input-width"
>
<div
class=
"space-between"
*ngIf=
" enableProxyCache && !registries.length"
>
<span
class=
"alert-label"
>
{{"REPLICATION.NO_ENDPOINT_INFO" | translate}}
</span>
<a
class=
"alert-label go-link"
routerLink=
"/harbor/registries"
>
{{'REPLICATION.ENDPOINTS' | translate}}
</a>
</div>
</div>
</div>
<div
class=
"clr-form-control mt-05"
*ngIf=
"isSystemAdmin && enableProxyCache"
>
<label
for=
"create_project_storage_limit"
class=
"clr-control-label"
></label>
<div
class=
"clr-control-container"
>
<div
class=
"clr-input-wrapper"
>
<label
class=
"clr-control-label endpoint"
>
{{ 'PROJECT.ENDPOINT' | translate }}
</label>
<input
placeholder=
"http(s)://192.168.1.1"
[value]=
"getEndpoint()"
readonly
class=
"clr-input"
type=
"text"
id=
"endpoint"
autocomplete=
"off"
>
</div>
</div>
</div>
</form>
</div>
<div
class=
"modal-footer"
>
...
...
src/portal/src/app/project/create-project/create-project.component.spec.ts
View file @
a13642c2
...
...
@@ -6,11 +6,14 @@ import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import
{
ClarityModule
}
from
'
@clr/angular
'
;
import
{
FormsModule
}
from
'
@angular/forms
'
;
import
{
MessageHandlerService
}
from
'
../../shared/message-handler/message-handler.service
'
;
import
{
ProjectService
}
from
"
../../../lib/services
"
;
import
{
EndpointDefaultService
,
EndpointService
,
ProjectService
}
from
'
../../../lib/services
'
;
import
{
BrowserAnimationsModule
}
from
'
@angular/platform-browser/animations
'
;
import
{
of
}
from
'
rxjs
'
;
import
{
delay
}
from
'
rxjs/operators
'
;
import
{
ErrorHandler
}
from
'
../../../lib/utils/error-handler
'
;
import
{
IServiceConfig
,
SERVICE_CONFIG
}
from
'
../../../lib/entities/service.config
'
;
import
{
CURRENT_BASE_HREF
}
from
'
../../../lib/utils/utils
'
;
import
{
HttpClientTestingModule
}
from
'
@angular/common/http/testing
'
;
describe
(
'
CreateProjectComponent
'
,
()
=>
{
let
component
:
CreateProjectComponent
;
...
...
@@ -31,10 +34,14 @@ describe('CreateProjectComponent', () => {
showSuccess
:
function
()
{
}
};
const
config
:
IServiceConfig
=
{
systemInfoEndpoint
:
CURRENT_BASE_HREF
+
"
/endpoints/testing
"
};
beforeEach
(
async
(()
=>
{
TestBed
.
configureTestingModule
({
imports
:
[
HttpClientTestingModule
,
BrowserAnimationsModule
,
FormsModule
,
ClarityModule
,
...
...
@@ -46,8 +53,10 @@ describe('CreateProjectComponent', () => {
],
providers
:
[
TranslateService
,
{
provide
:
SERVICE_CONFIG
,
useValue
:
config
},
{
provide
:
ProjectService
,
useValue
:
mockProjectService
},
{
provide
:
MessageHandlerService
,
useValue
:
mockMessageHandlerService
},
{
provide
:
EndpointService
,
useClass
:
EndpointDefaultService
},
ErrorHandler
]
}).
compileComponents
();
...
...
@@ -111,4 +120,13 @@ describe('CreateProjectComponent', () => {
const
modelBody
:
HTMLDivElement
=
fixture
.
nativeElement
.
querySelector
(
"
.modal-body
"
);
expect
(
modelBody
).
toBeFalsy
();
});
it
(
'
should enable proxy cache
'
,
async
()
=>
{
component
.
enableProxyCache
=
true
;
component
.
isSystemAdmin
=
true
;
fixture
.
detectChanges
();
await
fixture
.
whenStable
();
const
endpoint
:
HTMLDivElement
=
fixture
.
nativeElement
.
querySelector
(
"
#endpoint
"
);
expect
(
endpoint
).
toBeFalsy
();
});
});
src/portal/src/app/project/create-project/create-project.component.ts
View file @
a13642c2
...
...
@@ -30,7 +30,7 @@ import { MessageHandlerService } from "../../shared/message-handler/message-hand
import
{
InlineAlertComponent
}
from
"
../../shared/inline-alert/inline-alert.component
"
;
import
{
Project
}
from
"
../project
"
;
import
{
QuotaUnits
,
QuotaUnlimited
}
from
"
../../../lib/entities/shared.const
"
;
import
{
ProjectService
,
QuotaHardInterface
}
from
"
../../../lib/services
"
;
import
{
Endpoint
,
EndpointService
,
ProjectService
,
QuotaHardInterface
}
from
'
../../../lib/services
'
;
import
{
clone
,
getByte
,
GetIntegerAndUnit
,
validateLimit
}
from
"
../../../lib/utils/utils
"
;
...
...
@@ -39,7 +39,7 @@ import { clone, getByte, GetIntegerAndUnit, validateLimit } from "../../../lib/u
templateUrl
:
"
create-project.component.html
"
,
styleUrls
:
[
"
create-project.scss
"
]
})
export
class
CreateProjectComponent
implements
AfterViewInit
,
OnChanges
,
OnDestroy
{
export
class
CreateProjectComponent
implements
OnInit
,
AfterViewInit
,
OnChanges
,
OnDestroy
{
projectForm
:
NgForm
;
...
...
@@ -63,6 +63,8 @@ export class CreateProjectComponent implements AfterViewInit, OnChanges, OnDest
isNameExisted
:
boolean
=
false
;
nameTooltipText
=
"
PROJECT.NAME_TOOLTIP
"
;
checkOnGoing
=
false
;
enableProxyCache
:
boolean
=
false
;
endpoint
:
string
=
""
;
@
Output
()
create
=
new
EventEmitter
<
boolean
>
();
@
Input
()
quotaObj
:
QuotaHardInterface
;
@
Input
()
isSystemAdmin
:
boolean
;
...
...
@@ -70,11 +72,32 @@ export class CreateProjectComponent implements AfterViewInit, OnChanges, OnDest
inlineAlert
:
InlineAlertComponent
;
@
ViewChild
(
'
projectName
'
,
{
static
:
false
})
projectNameInput
:
ElementRef
;
checkNameSubscribe
:
Subscription
;
registries
:
Endpoint
[]
=
[];
supportedRegistryType
:
string
[]
=
[
'
docker-hub
'
,
'
harbor
'
];
constructor
(
private
projectService
:
ProjectService
,
private
translateService
:
TranslateService
,
private
messageHandlerService
:
MessageHandlerService
)
{
}
private
translateService
:
TranslateService
,
private
messageHandlerService
:
MessageHandlerService
,
private
endpointService
:
EndpointService
)
{
}
ngOnInit
():
void
{
this
.
getRegistries
();
}
getRegistries
()
{
this
.
endpointService
.
getEndpoints
()
.
subscribe
(
targets
=>
{
if
(
targets
&&
targets
.
length
)
{
this
.
registries
=
targets
.
filter
(
item
=>
this
.
supportedRegistryType
.
indexOf
(
item
.
type
)
!==
-
1
);
}
},
error
=>
{
this
.
messageHandlerService
.
handleError
(
error
);
});
}
ngAfterViewInit
():
void
{
ngAfterViewInit
():
void
{
if
(
!
this
.
checkNameSubscribe
)
{
this
.
checkNameSubscribe
=
fromEvent
(
this
.
projectNameInput
.
nativeElement
,
'
input
'
).
pipe
(
map
((
e
:
any
)
=>
e
.
target
.
value
),
...
...
@@ -161,7 +184,7 @@ export class CreateProjectComponent implements AfterViewInit, OnChanges, OnDest
this
.
isSubmitOnGoing
=
true
;
const
storageByte
=
+
this
.
storageLimit
===
QuotaUnlimited
?
this
.
storageLimit
:
getByte
(
+
this
.
storageLimit
,
this
.
storageLimitUnit
);
this
.
projectService
.
createProject
(
this
.
project
.
name
,
this
.
project
.
metadata
,
+
storageByte
)
.
createProject
(
this
.
project
.
name
,
this
.
project
.
metadata
,
+
storageByte
,
this
.
project
.
registry_id
)
.
subscribe
(
status
=>
{
this
.
isSubmitOnGoing
=
false
;
...
...
@@ -184,6 +207,8 @@ export class CreateProjectComponent implements AfterViewInit, OnChanges, OnDest
this
.
project
=
new
Project
();
this
.
hasChanged
=
false
;
this
.
createProjectOpened
=
true
;
this
.
enableProxyCache
=
false
;
this
.
endpoint
=
""
;
if
(
this
.
currentForm
&&
this
.
currentForm
.
controls
&&
this
.
currentForm
.
controls
[
"
create_project_name
"
])
{
this
.
currentForm
.
controls
[
"
create_project_name
"
].
reset
();
}
...
...
@@ -199,5 +224,16 @@ export class CreateProjectComponent implements AfterViewInit, OnChanges, OnDest
this
.
isNameValid
&&
!
this
.
checkOnGoing
;
}
getEndpoint
():
string
{
if
(
this
.
registries
&&
this
.
registries
.
length
&&
this
.
project
.
registry_id
)
{
for
(
let
i
=
0
;
i
<
this
.
registries
.
length
;
i
++
)
{
if
(
+
this
.
registries
[
i
].
id
===
+
this
.
project
.
registry_id
)
{
return
this
.
registries
[
i
].
url
;
}
}
}
return
''
;
}
}
src/portal/src/app/project/create-project/create-project.scss
View file @
a13642c2
...
...
@@ -15,4 +15,38 @@
.
clr-select-wrapper
:
:
after
{
right
:
0
.25rem
!
important
;
}
}
\ No newline at end of file
}
.input-width
{
width
:
242px
;
}
.mt-02
{
margin-top
:
0
.2rem
;
}
.width-164
{
width
:
164px
;
}
.endpoint
{
display
:
inline-block
;
width
:
72px
;
}
.display-none
{
display
:
none
}
.space-between
{
display
:
flex
;
justify-content
:
space-between
;
}
.go-link
{
line-height
:
1rem
;
cursor
:
pointer
;
}
.alert-label
{
color
:red
;
font-size
:
12px
;
}
.width-182
{
width
:
182px
;
}
.mt-05
{
margin-top
:
0
.5rem
;
}
src/portal/src/app/project/helm-chart/helm-chart-detail/chart-detail/chart-detail.component.spec.ts
View file @
a13642c2
...
...
@@ -140,6 +140,7 @@ describe('ChartDetailComponent', () => {
"
role_name
"
:
'
master
'
,
"
repo_count
"
:
0
,
"
chart_count
"
:
1
,
"
registry_id
"
:
0
,
"
metadata
"
:
{
"
public
"
:
"
true
"
,
"
enable_content_trust
"
:
"
string
"
,
...
...
src/portal/src/app/project/list-project/list-project.component.html
View file @
a13642c2
...
...
@@ -9,6 +9,7 @@
<clr-dg-column
[clrDgField]=
"'name'"
>
{{'PROJECT.NAME' | translate}}
</clr-dg-column>
<clr-dg-column
[clrDgSortBy]=
"accessLevelComparator"
>
{{'PROJECT.ACCESS_LEVEL' | translate}}
</clr-dg-column>
<clr-dg-column
[clrDgSortBy]=
"roleComparator"
>
{{'PROJECT.ROLE' | translate}}
</clr-dg-column>
<clr-dg-column
[clrDgSortBy]=
"typeComparator"
>
{{'PROJECT.TYPE' | translate}}
</clr-dg-column>
<clr-dg-column
[clrDgSortBy]=
"repoCountComparator"
>
{{'PROJECT.REPO_COUNT'| translate}}
</clr-dg-column>
<clr-dg-column
*ngIf=
"withChartMuseum"
[clrDgSortBy]=
"chartCountComparator"
>
{{'PROJECT.CHART_COUNT'| translate}}
</clr-dg-column>
<clr-dg-column
[clrDgSortBy]=
"timeComparator"
>
{{'PROJECT.CREATION_TIME' | translate}}
</clr-dg-column>
...
...
@@ -18,6 +19,7 @@
</clr-dg-cell>
<clr-dg-cell>
{{ (p.metadata.public === 'true' ? 'PROJECT.PUBLIC' : 'PROJECT.PRIVATE') | translate}}
</clr-dg-cell>
<clr-dg-cell>
{{ roleInfo[p.current_user_role_id]? (roleInfo[p.current_user_role_id] | translate): "-"}}
</clr-dg-cell>
<clr-dg-cell>
{{projectTypeMap[p.registry_id ? 1 : 0]}}
</clr-dg-cell>
<clr-dg-cell>
{{p.repo_count}}
</clr-dg-cell>
<clr-dg-cell
*ngIf=
"withChartMuseum"
>
{{p.chart_count}}
</clr-dg-cell>
<clr-dg-cell>
{{p.creation_time | date: 'short'}}
</clr-dg-cell>
...
...
src/portal/src/app/project/list-project/list-project.component.ts
View file @
a13642c2
...
...
@@ -55,11 +55,16 @@ export class ListProjectComponent implements OnDestroy {
timeComparator
:
Comparator
<
Project
>
=
new
CustomComparator
<
Project
>
(
"
creation_time
"
,
"
date
"
);
accessLevelComparator
:
Comparator
<
Project
>
=
new
CustomComparator
<
Project
>
(
"
public
"
,
"
string
"
);
roleComparator
:
Comparator
<
Project
>
=
new
CustomComparator
<
Project
>
(
"
current_user_role_id
"
,
"
number
"
);
typeComparator
:
Comparator
<
Project
>
=
new
CustomComparator
<
Project
>
(
"
registry_id
"
,
"
number
"
);
currentPage
=
1
;
totalCount
=
0
;
pageSize
=
15
;
currentState
:
State
;
subscription
:
Subscription
;
projectTypeMap
:
any
=
{
0
:
"
Project
"
,
1
:
"
Proxy Cache
"
};
constructor
(
private
session
:
SessionService
,
...
...
src/portal/src/app/project/project-detail/project-detail.component.html
View file @
a13642c2
<a
*ngIf=
"hasSignedIn"
(click)=
"backToProject()"
class=
"backStyle"
>
{{'PROJECT_DETAIL.PROJECTS' | translate}}
</a>
<a
*ngIf=
"!hasSignedIn"
[routerLink]=
"['/harbor', 'sign-in']"
>
{{'SEARCH.BACK' | translate}}
</a>
<h1
class=
"custom-h2"
sub-header-title
>
{{currentProject.name}}
<span
class=
"role-label"
*ngIf=
"isMember"
>
{{roleName | translate}}
</span></h1>
<h1
class=
"custom-h2"
sub-header-title
>
<clr-icon
*ngIf=
"isProxyCacheProject"
shape=
"cloud-traffic"
size=
"30"
></clr-icon>
<span
class=
"ml-05"
>
{{currentProject.name}}
</span>
<span
class=
"ml-05 role-label"
*ngIf=
"isMember"
>
{{roleName | translate}}
</span>
</h1>
<div
class=
"clr-row mt-0 line-height-10"
*ngIf=
"isProxyCacheProject"
>
<span
class=
"proxy-cache"
>
{{ 'PROJECT.PROXY_CACHE' | translate }}
</span>
</div>
<clr-tabs
id=
"project-tabs"
class=
"tabs"
[class.in-overflow]=
"isTabLinkInOverFlow()"
>
<ng-container
*ngFor=
"let tab of tabLinkNavList;let i=index"
>
<ng-container
*ngIf=
"tab.permissions()"
>
...
...
src/portal/src/app/project/project-detail/project-detail.component.scss
View file @
a13642c2
...
...
@@ -42,3 +42,15 @@ button {
padding
:
0
;
}
}
.ml-05
{
margin-left
:
0
.5rem
;
}
.proxy-cache
{
margin-left
:
2
.5rem
;
font-size
:
10px
;
font-weight
:
300
;
opacity
:
0
.8
;
}
.line-height-10
{
line-height
:
10px
;
}
src/portal/src/app/project/project-detail/project-detail.component.ts
View file @
a13642c2
...
...
@@ -118,6 +118,7 @@ export class ProjectDetailComponent implements OnInit, AfterViewInit, OnDestroy
previousWindowWidth
:
number
;
private
_subject
=
new
Subject
<
string
>
();
private
_subscription
:
Subscription
;
isProxyCacheProject
:
boolean
=
false
;
constructor
(
private
route
:
ActivatedRoute
,
private
router
:
Router
,
...
...
@@ -129,6 +130,9 @@ export class ProjectDetailComponent implements OnInit, AfterViewInit, OnDestroy
this
.
hasSignedIn
=
this
.
sessionService
.
getCurrentUser
()
!==
null
;
this
.
route
.
data
.
subscribe
(
data
=>
{
this
.
currentProject
=
<
Project
>
data
[
'
projectResolver
'
];
if
(
this
.
currentProject
.
registry_id
)
{
this
.
isProxyCacheProject
=
true
;
}
this
.
isMember
=
this
.
currentProject
.
is_member
;
this
.
roleName
=
this
.
currentProject
.
role_name
;
});
...
...
src/portal/src/app/project/project.ts
View file @
a13642c2
...
...
@@ -27,6 +27,7 @@ export class Project {
has_project_admin_role
:
boolean
;
is_member
:
boolean
;
role_name
:
string
;
registry_id
:
number
;
metadata
:
{
public
:
string
|
boolean
;
enable_content_trust
:
string
|
boolean
;
...
...
src/portal/src/app/project/summary/summary.component.html
View file @
a13642c2
<div
class=
"summary summary-dark display-flex"
*ngIf=
"summaryInformation"
>
<div
class=
"summary-left"
>
<div
class=
"display-flex project-detail pt-1"
>
<div
class=
"display-flex project-detail pt-05"
*ngIf=
"isSystemAdmin && endpoint"
>
<h5
class=
"mt-0 width-7-5"
>
{{'PROJECT.PROXY_CACHE_ENDPOINT' | translate}}
</h5>
<ul
class=
"list-unstyled"
>
<li
id=
"endpoint"
>
{{endpoint?.name}}-{{endpoint?.url}}
</li>
</ul>
</div>
<div
class=
"display-flex project-detail pt-05"
>
<h5
class=
"mt-0 width-7-5"
>
{{'SUMMARY.PROJECT_REPOSITORY' | translate}}
</h5>
<ul
class=
"list-unstyled"
>
<li>
{{summaryInformation?.repo_count}}
</li>
</ul>
</div>
<div
class=
"display-flex project-detail pt-
1
"
*ngIf=
"withHelmChart"
>
<div
class=
"display-flex project-detail pt-
05
"
*ngIf=
"withHelmChart"
>
<h5
class=
"mt-0 width-7-5"
>
{{'SUMMARY.PROJECT_HELM_CHART' | translate}}
</h5>
<ul
class=
"list-unstyled"
>
<li>
{{summaryInformation?.chart_count}}
</li>
</ul>
</div>
<div
*ngIf=
"showProjectMemberInfo"
class=
"display-flex project-detail pt-
1
"
>
<div
*ngIf=
"showProjectMemberInfo"
class=
"display-flex project-detail pt-
05
"
>
<h5
class=
"mt-0 width-7-5"
>
{{'SUMMARY.PROJECT_MEMBER' | translate}}
</h5>
<ul
class=
"list-unstyled"
>
<li>
{{ summaryInformation?.project_admin_count }} {{'SUMMARY.ADMIN' | translate}}
</li>
...
...
@@ -23,7 +29,7 @@
</ul>
</div>
</div>
<div
*ngIf=
"showQuotaInfo && summaryInformation?.quota"
class=
"summary-right pt-
1
"
>
<div
*ngIf=
"showQuotaInfo && summaryInformation?.quota"
class=
"summary-right pt-
05
"
>
<div
class=
"display-flex project-detail"
>
<h5
class=
"mt-0"
>
{{'SUMMARY.PROJECT_QUOTAS' | translate}}
</h5>
<div
class=
"ml-1"
>
...
...
@@ -55,4 +61,4 @@
</div>
</div>
</div>
\ No newline at end of file
</div>
src/portal/src/app/project/summary/summary.component.scss
View file @
a13642c2
...
...
@@ -57,3 +57,7 @@
}
}
}
.pt-05
{
padding-top
:
0
.5rem
;
}
src/portal/src/app/project/summary/summary.component.spec.ts
View file @
a13642c2
...
...
@@ -6,14 +6,23 @@ import { ActivatedRoute } from '@angular/router';
import
{
of
}
from
'
rxjs
'
;
import
{
AppConfigService
}
from
"
../../services/app-config.service
"
;
import
{
SummaryComponent
}
from
'
./summary.component
'
;
import
{
ProjectService
,
UserPermissionService
}
from
"
../../../lib/services
"
;
import
{
EndpointDefaultService
,
EndpointService
,
ProjectService
,
UserPermissionService
}
from
'
../../../lib/services
'
;
import
{
ErrorHandler
}
from
"
../../../lib/utils/error-handler
"
;
import
{
IServiceConfig
,
SERVICE_CONFIG
}
from
'
../../../lib/entities/service.config
'
;
import
{
CURRENT_BASE_HREF
}
from
'
../../../lib/utils/utils
'
;
import
{
SessionService
}
from
'
../../shared/session.service
'
;
describe
(
'
SummaryComponent
'
,
()
=>
{
let
component
:
SummaryComponent
;
let
fixture
:
ComponentFixture
<
SummaryComponent
>
;
let
fakeAppConfigService
=
null
;
let
fakeAppConfigService
=
{
getConfig
()
{
return
{
with_chartmuseum
:
false
};
}
};
let
fakeProjectService
=
{
getProjectSummary
:
function
()
{
return
of
();
...
...
@@ -25,6 +34,34 @@ describe('SummaryComponent', () => {
return
of
([
true
,
true
]);
}
};
const
config
:
IServiceConfig
=
{
systemInfoEndpoint
:
CURRENT_BASE_HREF
+
"
/endpoints/testing
"
};
const
fakedSessionService
=
{
getCurrentUser
()
{
return
{
has_admin_role
:
true
};
}
};
const
fakedEndpointService
=
{
getEndpoint
()
{
return
of
({
name
:
"
test
"
,
url
:
"
https://test.com
"
});
}
};
const
mockedSummaryInformation
=
{
repo_count
:
0
,
chart_count
:
0
,
project_admin_count
:
1
,
master_count
:
0
,
developer_count
:
0
};
beforeEach
(
async
(()
=>
{
TestBed
.
configureTestingModule
({
...
...
@@ -42,14 +79,19 @@ describe('SummaryComponent', () => {
{
provide
:
ProjectService
,
useValue
:
fakeProjectService
},
{
provide
:
ErrorHandler
,
useValue
:
fakeErrorHandler
},
{
provide
:
UserPermissionService
,
useValue
:
fakeUserPermissionService
},
{
provide
:
EndpointService
,
useValue
:
fakedEndpointService
},
{
provide
:
SERVICE_CONFIG
,
useValue
:
config
},
{
provide
:
SessionService
,
useValue
:
fakedSessionService
},
{
provide
:
ActivatedRoute
,
useValue
:
{
paramMap
:
of
({
get
:
(
key
)
=>
'
value
'
}),
snapshot
:
{
parent
:
{
params
:
{
id
:
1
}
params
:
{
id
:
1
},
data
:
{
projectResolver
:
{
registry_id
:
3
}
}
},
data
:
1
}
}
},
...
...
@@ -66,4 +108,13 @@ describe('SummaryComponent', () => {
it
(
'
should create
'
,
()
=>
{
expect
(
component
).
toBeTruthy
();
});
it
(
'
should show proxy cache endpoint
'
,
async
()
=>
{
component
.
summaryInformation
=
mockedSummaryInformation
;
fixture
.
detectChanges
();
await
fixture
.
whenStable
();
const
endpoint
:
HTMLElement
=
fixture
.
nativeElement
.
querySelector
(
"
#endpoint
"
);
expect
(
endpoint
).
toBeTruthy
();
expect
(
endpoint
.
innerText
).
toEqual
(
"
test-https://test.com
"
);
});
});
src/portal/src/app/project/summary/summary.component.ts
View file @
a13642c2
import
{
Component
,
Input
,
OnInit
}
from
'
@angular/core
'
;
import
{
Component
,
OnInit
}
from
'
@angular/core
'
;
import
{
ActivatedRoute
}
from
'
@angular/router
'
;
import
{
AppConfigService
}
from
"
../../services/app-config.service
"
;
import
{
QUOTA_DANGER_COEFFICIENT
,
QUOTA_WARNING_COEFFICIENT
,
QuotaUnits
}
from
"
../../../lib/entities/shared.const
"
;
import
{
ProjectService
,
UserPermissionService
,
USERSTATICPERMISSION
}
from
"
../../../lib/services
"
;
import
{
Endpoint
,
EndpointService
,
ProjectService
,
UserPermissionService
,
USERSTATICPERMISSION
}
from
'
../../../lib/services
'
;
import
{
ErrorHandler
}
from
"
../../../lib/utils/error-handler
"
;
import
{
clone
,
GetIntegerAndUnit
,
getSuitableUnit
as
getSuitableUnitFn
}
from
"
../../../lib/utils/utils
"
;
import
{
SessionService
}
from
'
../../shared/session.service
'
;
import
{
Project
}
from
'
../project
'
;
@
Component
({
selector
:
'
summary
'
,
...
...
@@ -19,20 +27,30 @@ export class SummaryComponent implements OnInit {
summaryInformation
:
any
;
quotaDangerCoefficient
:
number
=
QUOTA_DANGER_COEFFICIENT
;
quotaWarningCoefficient
:
number
=
QUOTA_WARNING_COEFFICIENT
;
endpoint
:
Endpoint
;
constructor
(
private
projectService
:
ProjectService
,
private
userPermissionService
:
UserPermissionService
,
private
errorHandler
:
ErrorHandler
,
private
appConfigService
:
AppConfigService
,
private
route
:
ActivatedRoute
private
route
:
ActivatedRoute
,
private
session
:
SessionService
,
private
endpointService
:
EndpointService
)
{
}
ngOnInit
()
{
this
.
projectId
=
this
.
route
.
snapshot
.
parent
.
params
[
'
id
'
];
const
resolverData
=
this
.
route
.
snapshot
.
parent
.
data
;
if
(
resolverData
)
{
const
pro
:
Project
=
<
Project
>
resolverData
[
'
projectResolver
'
];
if
(
pro
&&
pro
.
registry_id
&&
this
.
isSystemAdmin
)
{
this
.
getRegistry
(
pro
.
registry_id
);
}
}
const
permissions
=
[
{
resource
:
USERSTATICPERMISSION
.
MEMBER
.
KEY
,
action
:
USERSTATICPERMISSION
.
MEMBER
.
VALUE
.
LIST
},
{
resource
:
USERSTATICPERMISSION
.
QUOTA
.
KEY
,
action
:
USERSTATICPERMISSION
.
QUOTA
.
VALUE
.
READ
},
{
resource
:
USERSTATICPERMISSION
.
MEMBER
.
KEY
,
action
:
USERSTATICPERMISSION
.
MEMBER
.
VALUE
.
LIST
},
{
resource
:
USERSTATICPERMISSION
.
QUOTA
.
KEY
,
action
:
USERSTATICPERMISSION
.
QUOTA
.
VALUE
.
READ
},
];
this
.
userPermissionService
.
hasProjectPermissions
(
this
.
projectId
,
permissions
).
subscribe
((
results
:
Array
<
boolean
>
)
=>
{
...
...
@@ -47,6 +65,14 @@ export class SummaryComponent implements OnInit {
});
}
getRegistry
(
registryId
:
number
)
{
this
.
endpointService
.
getEndpoint
(
registryId
).
subscribe
(
res
=>
{
this
.
endpoint
=
res
;
},
error
=>
{
this
.
errorHandler
.
error
(
error
);
});
}
getSuitableUnit
(
value
)
{
const
QuotaUnitsCopy
=
clone
(
QuotaUnits
);
return
getSuitableUnitFn
(
value
,
QuotaUnitsCopy
);
...
...
@@ -60,4 +86,9 @@ export class SummaryComponent implements OnInit {
return
this
.
appConfigService
.
getConfig
().
with_chartmuseum
;
}
public
get
isSystemAdmin
():
boolean
{
const
account
=
this
.
session
.
getCurrentUser
();
return
account
&&
account
.
has_admin_role
;
}