Merge remote-tracking branch 'upstream/new-ui-with-sync-image' into job-service

This commit is contained in:
Tan Jiang 2016-06-30 14:06:45 +08:00
commit 9107de8744
33 changed files with 337 additions and 113 deletions

View File

@ -98,7 +98,8 @@ render(os.path.join(templates_dir, "ui", "env"),
ldap_url=ldap_url,
ldap_basedn=ldap_basedn,
self_registration=self_registration,
ui_secret=ui_secret)
ui_secret=ui_secret,
verify_remote_cert=verify_remote_cert)
render(os.path.join(templates_dir, "ui", "app.conf"),
ui_conf,

View File

@ -17,3 +17,4 @@ LOG_LEVEL=debug
GODEBUG=netdns=cgo
EXT_ENDPOINT=$ui_url
TOKEN_URL=http://ui
VERIFY_REMOTE_CERT=$verify_remote_cert

View File

@ -19,6 +19,7 @@ import (
"encoding/json"
"fmt"
"net/http"
"os"
"strconv"
"github.com/astaxie/beego/validation"
@ -136,3 +137,14 @@ func (b *BaseAPI) GetIDFromURL() int64 {
return id
}
func getIsInsecure() bool {
insecure := false
verifyRemoteCert := os.Getenv("VERIFY_REMOTE_CERT")
if verifyRemoteCert == "off" {
insecure = true
}
return insecure
}

View File

@ -21,6 +21,7 @@ import (
"io/ioutil"
"net/http"
"strconv"
"time"
"github.com/vmware/harbor/dao"
"github.com/vmware/harbor/models"
@ -59,6 +60,7 @@ func (ra *RepJobAPI) Prepare() {
func (ra *RepJobAPI) List() {
var policyID int64
var repository, status string
var startTime, endTime *time.Time
var err error
policyIDStr := ra.GetString("policy_id")
@ -69,10 +71,36 @@ func (ra *RepJobAPI) List() {
}
}
endTimeStr := ra.GetString("end_time")
if len(endTimeStr) != 0 {
i, err := strconv.ParseInt(endTimeStr, 10, 64)
if err != nil {
ra.CustomAbort(http.StatusBadRequest, "invalid end_time")
}
t := time.Unix(i, 0)
endTime = &t
}
startTimeStr := ra.GetString("start_time")
if len(startTimeStr) != 0 {
i, err := strconv.ParseInt(startTimeStr, 10, 64)
if err != nil {
ra.CustomAbort(http.StatusBadRequest, "invalid start_time")
}
t := time.Unix(i, 0)
startTime = &t
}
if startTime == nil && endTime == nil {
// if start_time and end_time are both null, list jobs of last 10 days
t := time.Now().UTC().AddDate(0, 0, -10)
startTime = &t
}
repository = ra.GetString("repository")
status = ra.GetString("status")
jobs, err := dao.FilterRepJobs(policyID, repository, status)
jobs, err := dao.FilterRepJobs(policyID, repository, status, startTime, endTime, 1000)
if err != nil {
log.Errorf("failed to filter jobs according policy ID %d, repository %s, status %s: %v", policyID, repository, status, err)
ra.RenderError(http.StatusInternalServerError, "Failed to query job")

View File

@ -144,6 +144,18 @@ func (ra *RepositoryAPI) Delete() {
tags = append(tags, tag)
}
project := ""
if strings.Contains(repoName, "/") {
project = repoName[0:strings.LastIndex(repoName, "/")]
}
user, _, ok := ra.Ctx.Request.BasicAuth()
if !ok {
user, err = ra.getUsername()
if err != nil {
log.Errorf("failed to get user: %v", err)
}
}
for _, t := range tags {
if err := rc.DeleteTag(t); err != nil {
if regErr, ok := err.(*registry_error.Error); ok {
@ -156,6 +168,11 @@ func (ra *RepositoryAPI) Delete() {
log.Infof("delete tag: %s %s", repoName, t)
go TriggerReplicationByRepository(repoName, []string{t}, models.RepOpDelete)
go func(tag string) {
if err := dao.AccessLog(user, project, repoName, tag, "delete"); err != nil {
log.Errorf("failed to add access log: %v", err)
}
}(t)
}
go func() {
@ -164,7 +181,6 @@ func (ra *RepositoryAPI) Delete() {
log.Errorf("error occurred while refresh catalog cache: %v", err)
}
}()
}
type tag struct {
@ -255,12 +271,10 @@ func (ra *RepositoryAPI) GetManifests() {
func (ra *RepositoryAPI) initRepositoryClient(repoName string) (r *registry.Repository, err error) {
endpoint := os.Getenv("REGISTRY_URL")
// TODO read variable from config file
insecure := true
username, password, ok := ra.Ctx.Request.BasicAuth()
if ok {
return newRepositoryClient(endpoint, insecure, username, password,
return newRepositoryClient(endpoint, getIsInsecure(), username, password,
repoName, "repository", repoName, "pull", "push", "*")
}
@ -269,7 +283,7 @@ func (ra *RepositoryAPI) initRepositoryClient(repoName string) (r *registry.Repo
return nil, err
}
return cache.NewRepositoryClient(endpoint, insecure, username, repoName,
return cache.NewRepositoryClient(endpoint, getIsInsecure(), username, repoName,
"repository", repoName, "pull", "push", "*")
}

View File

@ -92,9 +92,7 @@ func (t *TargetAPI) Ping() {
password = t.GetString("password")
}
// TODO read variable from config file
insecure := true
registry, err := newRegistryClient(endpoint, insecure, username, password,
registry, err := newRegistryClient(endpoint, getIsInsecure(), username, password,
"", "", "")
if err != nil {
// timeout, dns resolve error, connection refused, etc.

View File

@ -55,16 +55,23 @@ func (b *BaseController) Prepare() {
b.SetSession("Lang", lang)
lang = sessionLang.(string)
} else {
lang = defaultLang
al := b.Ctx.Request.Header.Get("Accept-Language")
if len(al) > 4 {
al = al[:5] // Only compare first 5 letters.
if i18n.IsExist(al) {
lang = al
}
}
}
}
b.SetSession("Lang", lang)
if _, exist := supportLanguages[lang]; !exist { //Check if support the request language.
lang = defaultLang //Set default language if not supported.
}
b.Ctx.SetCookie("language", lang, 0, "/")
b.SetSession("Lang", lang)
curLang := langType{
Lang: lang,
}

View File

@ -1180,7 +1180,7 @@ func TestDeleteRepJob(t *testing.T) {
}
func TestFilterRepJobs(t *testing.T) {
jobs, err := FilterRepJobs(policyID, "", "")
jobs, err := FilterRepJobs(policyID, "", "", nil, nil, 1000)
if err != nil {
log.Errorf("Error occured in FilterRepJobs: %v, policy ID: %d", err, policyID)
return

View File

@ -311,7 +311,8 @@ func GetRepJobByPolicy(policyID int64) ([]*models.RepJob, error) {
}
// FilterRepJobs filters jobs by repo and policy ID
func FilterRepJobs(policyID int64, repository, status string) ([]*models.RepJob, error) {
func FilterRepJobs(policyID int64, repository, status string, startTime,
endTime *time.Time, limit int) ([]*models.RepJob, error) {
o := GetOrmer()
qs := o.QueryTable(new(models.RepJob))
@ -324,6 +325,21 @@ func FilterRepJobs(policyID int64, repository, status string) ([]*models.RepJob,
if len(status) != 0 {
qs = qs.Filter("Status__icontains", status)
}
if startTime != nil {
fmt.Printf("%v\n", startTime)
qs = qs.Filter("CreationTime__gte", startTime)
}
if endTime != nil {
fmt.Printf("%v\n", endTime)
qs = qs.Filter("CreationTime__lte", endTime)
}
if limit != 0 {
qs = qs.Limit(limit)
}
qs = qs.OrderBy("-CreationTime")
var jobs []*models.RepJob

View File

@ -39,55 +39,85 @@ const manifestPattern = `^application/vnd.docker.distribution.manifest.v\d\+json
// Post handles POST request, and records audit log or refreshes cache based on event.
func (n *NotificationHandler) Post() {
var notification models.Notification
//log.Info("Notification Handler triggered!\n")
// log.Infof("request body in string: %s", string(n.Ctx.Input.CopyBody()))
err := json.Unmarshal(n.Ctx.Input.CopyBody(1<<32), &notification)
if err != nil {
log.Errorf("error while decoding json: %v", err)
log.Errorf("failed to decode notification: %v", err)
return
}
var username, action, repo, project, repoTag string
var matched bool
for _, e := range notification.Events {
matched, err = regexp.MatchString(manifestPattern, e.Target.MediaType)
if err != nil {
log.Errorf("Failed to match the media type against pattern, error: %v", err)
matched = false
events, err := filterEvents(&notification)
if err != nil {
log.Errorf("failed to filter events: %v", err)
return
}
for _, event := range events {
repository := event.Target.Repository
project := ""
if strings.Contains(repository, "/") {
project = repository[0:strings.LastIndex(repository, "/")]
}
if matched && (strings.HasPrefix(e.Request.UserAgent, "docker") ||
strings.ToLower(strings.TrimSpace(e.Request.UserAgent)) == "harbor-registry-client") {
username = e.Actor.Name
action = e.Action
repo = e.Target.Repository
repoTag = e.Target.Tag
log.Debugf("repo tag is : %v ", repoTag)
if strings.Contains(repo, "/") {
project = repo[0:strings.LastIndex(repo, "/")]
}
if username == "" {
username = "anonymous"
}
tag := event.Target.Tag
action := event.Action
if action == "pull" && username == "job-service-user" {
return
}
user := event.Actor.Name
if len(user) == 0 {
user = "anonymous"
}
go dao.AccessLog(username, project, repo, repoTag, action)
go func() {
if err := dao.AccessLog(user, project, repository, tag, action); err != nil {
log.Errorf("failed to add access log: %v", err)
}
}()
if action == "push" {
go func() {
if err := cache.RefreshCatalogCache(); err != nil {
log.Errorf("failed to refresh cache: %v", err)
}
}()
operation := ""
if action == "push" {
go func() {
err2 := cache.RefreshCatalogCache()
if err2 != nil {
log.Errorf("Error happens when refreshing cache: %v", err2)
}
}()
go api.TriggerReplicationByRepository(repo, []string{repoTag}, models.RepOpTransfer)
operation = models.RepOpTransfer
}
go api.TriggerReplicationByRepository(repository, []string{tag}, operation)
}
}
}
func filterEvents(notification *models.Notification) ([]*models.Event, error) {
events := []*models.Event{}
for _, event := range notification.Events {
isManifest, err := regexp.MatchString(manifestPattern, event.Target.MediaType)
if err != nil {
log.Errorf("failed to match the media type against pattern: %v", err)
continue
}
if !isManifest {
continue
}
//pull and push manifest by docker-client
if strings.HasPrefix(event.Request.UserAgent, "docker") && (event.Action == "pull" || event.Action == "push") {
events = append(events, &event)
continue
}
//push manifest by docker-client or job-service
if strings.ToLower(strings.TrimSpace(event.Request.UserAgent)) == "harbor-registry-client" && event.Action == "push" {
events = append(events, &event)
continue
}
}
return events, nil
}
// Render returns nil as it won't render any template.

View File

@ -1,6 +1,6 @@
.up-section .up-table-pane {
overflow-y: auto;
height: 180px;
height: 220px;
margin-top: -10px;
}

View File

@ -73,4 +73,8 @@
.color-warning {
color: #f0ad4e;
}
.label-custom {
margin: 0 5px 0 10px;
}

View File

@ -53,7 +53,11 @@
console.log('vm.projects is undefined, load public projects.');
}
vm.selectedProject = vm.projects[0];
if(angular.isArray(vm.projects) && vm.projects.length > 0) {
vm.selectedProject = vm.projects[0];
}else{
$window.location.href = '/project';
}
if(getParameterByName('project_id', $location.absUrl())){
angular.forEach(vm.projects, function(value, index) {
@ -64,8 +68,8 @@
}
$location.search('project_id', vm.selectedProject.project_id);
vm.checkProjectMember(vm.selectedProject.project_id);
vm.checkProjectMember(vm.selectedProject.project_id);
vm.resultCount = vm.projects.length;
$scope.$watch('vm.filterInput', function(current, origin) {

View File

@ -49,6 +49,7 @@
function addProjectMemberComplete(data, status, header) {
console.log('addProjectMemberComplete: status:' + status + ', data:' + data);
vm.reload();
vm.isOpen = false;
}
function addProjectMemberFailed(data, status, headers) {
@ -97,4 +98,4 @@
}
}
})();
})();

View File

@ -36,6 +36,7 @@
$scope.$emit('addedSuccess', true);
vm.hasError = false;
vm.errorMessage = '';
vm.isOpen = false;
}
function addProjectFailed(data, status) {
@ -91,4 +92,4 @@
}
}
})();
})();

View File

@ -92,7 +92,7 @@
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary" id="btnOk" ng-click="form.$valid && vm.save(replication)">// 'ok' | tr //</button>
<button ng-show="vm.targetEditable" type="submit" class="btn btn-primary" ng-click="vm.save(replication)">// 'ok' | tr //</button>
<button type="button" class="btn btn-default" data-dismiss="modal">// 'close' | tr //</button>
</div>
</form>

View File

@ -69,7 +69,7 @@
$scope.$watch('vm.targetId', function(current) {
if(current) {
vm1.selection.id = current;
vm1.selection.id = current || vm.destinations[0].id;
}
});
@ -125,15 +125,19 @@
function saveOrUpdateDestination() {
var target = {
'name' : vm1.name,
'endpoint': vm1.endpoint,
'username': vm1.username,
'password': vm1.password
};
if(vm.checkedAddTarget) {
target.name = vm1.name;
if(vm.checkedAddTarget){
CreateDestinationService(target.name, target.endpoint, target.username, target.password)
.success(createDestinationSuccess)
.error(createDestinationFailed);
}else{
vm.policy.targetId = vm1.selection.id;
saveOrUpdatePolicy();
}
}
@ -154,7 +158,10 @@
function update(policy) {
vm.policy = policy;
saveOrUpdateDestination();
if(vm.targetEditable) {
vm.policy.targetId = vm1.selection.id;
saveOrUpdatePolicy();
}
}
function pingDestination() {

View File

@ -28,18 +28,18 @@
<table class="table table-pane">
<tbody>
<tr ng-if="vm.replicationPolicies.length == 0">
<td colspan="7" height="100%" class="empty-hint" ><h3 class="text-muted">// 'no_replication_policies' | tr //</h3></td>
<td colspan="7" height="100%" class="empty-hint" ><h3 class="text-muted">// 'no_replication_policies_add_new' | tr //</h3></td>
</tr>
<tr policy_id="//r.id//" ng-if="vm.replicationPolicies.length > 0" ng-repeat="r in vm.replicationPolicies" value="//vm.last = $last//">
<td width="10%">//r.name//</td>
<td width="18%">//r.description//</td>
<td width="18%">//r.target_name//</td>
<td width="18%">//r.start_time | dateL : 'YYYY-MM-DD HH:mm:ss'//</td>
<td width="14%" ng-switch on="//r.enabled//">
<td width="18%" style="padding-left: 10px;">//r.description//</td>
<td width="18%" style="padding-left: 15px;">//r.target_name//</td>
<td width="18%" style="padding-left: 18px;">//r.start_time | dateL : 'YYYY-MM-DD HH:mm:ss'//</td>
<td width="14%" style="padding-left: 18px;" ng-switch on="//r.enabled//">
<span ng-switch-when="1">// 'enabled' | tr //</span>
<span ng-switch-when="0">// 'disabled' | tr //</span>
</td>
<td width="15%">
<td width="15%" style="padding-left: 20px;">
<div class="display-inline-block" ng-switch on="//r.enabled//">
<a href="javascript:void(0);" ng-click="vm.togglePolicy(r.id, 0)" title="// 'disabled' | tr //"><span ng-switch-when="1" class="glyphicon glyphicon-stop color-danger"></span></a>
<a href="javascript:void(0);" ng-click="vm.togglePolicy(r.id, 1)" title="// 'enabled' | tr //"><span ng-switch-when="0" class="glyphicon glyphicon-play color-success"></span></a>
@ -66,6 +66,30 @@
<button class="btn btn-primary" type="button" ng-click="vm.searchReplicationJob()" ng-disabled="vm.refreshPending"><span class="glyphicon glyphicon-search"></span></button>
</span>
</div>
<div class="input-group">
<label for="selStatus" class="control-label label-custom">// 'status' | tr //:</label>
<div class="input-group">
<select class="form-control" id="selStatus" ng-options="st.value for st in vm.jobStatus() track by st.key" ng-model="vm.currentStatus" ng-change="vm.searchReplicationJob()"></select>
</div>
</div>
<div class="input-group">
<label for="fromDatePicker" class="control-label label-custom">// 'from' | tr //:</label>
<div class="input-group datetimepicker">
<input id="fromDatePicker" class="form-control" type="text" readonly="readonly" ng-model="vm.fromDate" ng-change="vm.pickUp({key:'fromDate', value: vm.fromDate})" size="10">
<span class="input-group-addon">
<a href="javascript:void(0);"><span class="glyphicon glyphicon-calendar"></span></a>
</span>
</div>
</div>
<div class="input-group">
<label for="toDatePicker" class="control-label label-custom">// 'to' | tr //:</label>
<div class="input-group datetimepicker">
<input id="toDatePicker" class="form-control" type="text" readonly="readonly" ng-model="vm.toDate" ng-change="vm.pickUp({key:'toDate', value: vm.toDate})" size="10">
<span class="input-group-addon">
<a href="javascript:void(0);"><span class="glyphicon glyphicon-calendar"></span></a>
</span>
</div>
</div>
<div class="input-group">
<button type="button" class="btn btn-success" ng-click="vm.searchReplicationJob()"><span class="glyphicon glyphicon-refresh"></span></button>
</div>
@ -94,11 +118,13 @@
<tr ng-if="vm.replicationJobs.length > 0" ng-repeat="r in vm.replicationJobs">
<td width="15%">//r.repository//</td>
<td width="12%">//r.operation//</td>
<td width="18%">//r.creation_time | dateL : 'YYYY-MM-DD HH:mm:ss'//</td>
<td width="18%">//r.creation_time | dateL : 'YYYY-MM-DD HH:mm:ss'//</td>
<td width="18%">//r.update_time | dateL : 'YYYY-MM-DD HH:mm:ss'//</td>
<td width="9%">//r.status//</td>
<td width="10%" align="left"><a href="javascript:void(0);" ng-click="vm.downloadLog(r.id)" title="// 'download_log' | tr //"><span style="margin-left: 10px;" class="glyphicon glyphicon-file"></span></a></td>
<td width="18%" style="padding-left: 15px;">//r.creation_time | dateL : 'YYYY-MM-DD HH:mm:ss'//</td>
<td width="18%" style="padding-left: 15px;">//r.creation_time | dateL : 'YYYY-MM-DD HH:mm:ss'//</td>
<td width="18%" style="padding-left: 18px;">//r.update_time | dateL : 'YYYY-MM-DD HH:mm:ss'//</td>
<td width="9%" style="padding-left: 20px;">//r.status//</td>
<td width="10%" align="left">
<a ng-show="r.status != 'canceled' && r.status != 'pending'" href="javascript:void(0);" ng-click="vm.downloadLog(r.id)" title="// 'download_log' | tr //"><span style="margin-left: 10px;" class="glyphicon glyphicon-file"></span></a>
</td>
</tr>
</tbody>
</table>

View File

@ -4,11 +4,27 @@
angular
.module('harbor.replication')
.directive('listReplication', listReplication);
ListReplicationController.$inject = ['$scope', 'getParameterByName', '$location', 'ListReplicationPolicyService', 'ToggleReplicationPolicyService', 'ListReplicationJobService', '$window', '$filter', 'trFilter'];
.directive('listReplication', listReplication)
.factory('jobStatus', jobStatus);
jobStatus.inject = ['$filter', 'trFilter'];
function jobStatus($filter, trFilter) {
return function() {
return [
{'key': 'all' , 'value': $filter('tr')('all')},
{'key': 'pending', 'value': $filter('tr')('pending')},
{'key': 'running', 'value': $filter('tr')('running')},
{'key': 'error' , 'value': $filter('tr')('error')},
{'key': 'stopped', 'value': $filter('tr')('stopped')},
{'key': 'finished', 'value':$filter('tr')('finished')},
{'key': 'canceled', 'value': $filter('tr')('canceled')}
];
}
}
function ListReplicationController($scope, getParameterByName, $location, ListReplicationPolicyService, ToggleReplicationPolicyService, ListReplicationJobService, $window, $filter, trFilter) {
ListReplicationController.$inject = ['$scope', 'getParameterByName', '$location', 'ListReplicationPolicyService', 'ToggleReplicationPolicyService', 'ListReplicationJobService', '$window', '$filter', 'trFilter', 'jobStatus'];
function ListReplicationController($scope, getParameterByName, $location, ListReplicationPolicyService, ToggleReplicationPolicyService, ListReplicationJobService, $window, $filter, trFilter, jobStatus) {
var vm = this;
vm.sectionHeight = {'min-height': '1200px'};
@ -36,6 +52,9 @@
vm.retrievePolicy();
vm.refreshPending = false;
vm.jobStatus = jobStatus;
vm.currentStatus = vm.jobStatus()[0];
function searchReplicationPolicy() {
vm.refreshPending = true;
vm.retrievePolicy();
@ -54,7 +73,8 @@
}
function retrieveJob(policyId) {
ListReplicationJobService(policyId, vm.replicationJobName)
var status = (vm.currentStatus.key === 'all' ? '' : vm.currentStatus.key);
ListReplicationJobService(policyId, vm.replicationJobName, status)
.success(listReplicationJobSuccess)
.error(listReplicationJobFailed);
}
@ -70,6 +90,18 @@
function listReplicationJobSuccess(data, status) {
vm.replicationJobs = data || [];
angular.forEach(vm.replicationJobs, function(item) {
for(var key in item) {
var value = item[key]
switch(key) {
case 'operation':
case 'status':
item[key] = $filter('tr')(value);
default:
break;
}
}
});
vm.refreshPending = false;
}
@ -193,6 +225,19 @@
ctrl.retrieveJob($(this).attr('policy_id'));
ctrl.lastPolicyId = $(this).attr('policy_id');
}
element.find('.datetimepicker').datetimepicker({
locale: 'en-US',
ignoreReadonly: true,
format: 'L',
showClear: true
});
element.find('#fromDatePicker').on('blur', function(){
ctrl.pickUp({'key': 'fromDate', 'value': $(this).val()});
});
element.find('#toDatePicker').on('blur', function(){
ctrl.pickUp({'key': 'toDate', 'value': $(this).val()});
});
}
}

View File

@ -1,4 +1,4 @@
<div class="tab-pane active" id="repositories" element-height>
<div class="tab-pane" id="repositories" element-height>
<div class="col-xs-12 col-md-12 each-tab-pane">
<div class="form-inline">
<div class="input-group">
@ -8,24 +8,26 @@
</span>
</div>
</div>
<div ng-if="vm.repositories.length === 0" class="empty-hint">
<h3 style="margin-top: 200px;" class="text-muted">// 'no_repositories' | tr //</h3>
</div>
<div class="sub-pane">
<div ng-if="vm.repositories.length > 0" class="panel-group" id="accordion" role="tablist" aria-multiselectable="true">
<modal-dialog action="vm.deleteImage()" content-type="text/html" modal-title="//vm.modalTitle//" modal-message="//vm.modalMessage//"></modal-dialog>
<div class="panel panel-default" ng-repeat="repo in vm.repositories" ng-show="vm.tagCount[repo] > 0">
<div class="panel-heading" role="tab" id="heading//$index + 1//">
<h4 class="panel-title">
<a role="button" data-toggle="collapse" data-parent="" href="?project_id=//vm.projectId//#collapse//$index + 1//" aria-expanded="true" aria-controls="collapse//$index+1//">
<span class="glyphicon glyphicon-book"></span> &nbsp;//repo// &nbsp;&nbsp;<span class="badge">//vm.tagCount[repo]//</span>
</a>
<a class="pull-right" style="margin-right: 78px;" href="javascript:void(0)" data-toggle="modal" data-target="#myModal" ng-click="vm.deleteByRepo(repo)" title="// 'delete_repo' | tr //"><span class="glyphicon glyphicon-trash"></span></a>
</h4>
<div class="pane">
<div class="sub-pane">
<div ng-if="vm.repositories.length === 0" class="empty-hint">
<h3 style="margin-top: 120px;" class="text-muted">// 'no_repositories' | tr //</h3>
</div>
<div ng-if="vm.repositories.length > 0" class="panel-group" id="accordion" role="tablist" aria-multiselectable="true">
<modal-dialog action="vm.deleteImage()" content-type="text/html" modal-title="//vm.modalTitle//" modal-message="//vm.modalMessage//"></modal-dialog>
<div class="panel panel-default" ng-repeat="repo in vm.repositories" ng-show="vm.tagCount[repo] > 0">
<div class="panel-heading" role="tab" id="heading//$index + 1//">
<h4 class="panel-title">
<a role="button" data-toggle="collapse" data-parent="" href="?project_id=//vm.projectId//#collapse//$index + 1//" aria-expanded="true" aria-controls="collapse//$index+1//">
<span class="glyphicon glyphicon-book"></span> &nbsp;//repo// &nbsp;&nbsp;<span class="badge">//vm.tagCount[repo]//</span>
</a>
<a class="pull-right" style="margin-right: 78px;" href="javascript:void(0)" data-toggle="modal" data-target="#myModal" ng-click="vm.deleteByRepo(repo)" title="// 'delete_repo' | tr //"><span class="glyphicon glyphicon-trash"></span></a>
</h4>
</div>
<list-tag associate-id="$index + 1" repo-name="repo" tag-count="vm.tagCount"></list-tag>
</div>
</div>
<list-tag associate-id="$index + 1" repo-name="repo" tag-count="vm.tagCount"></list-tag>
</div>
</div>
</div>
</div>
</div>

View File

@ -1,4 +1,7 @@
<h4 class="page-header title-color underlined">// 'summary' | tr //</h4>
<h4 class="page-header title-color underlined">// 'summary' | tr //</h4>
<dl class="page-content dl-horizontal" ng-repeat="(key, value) in vm.statProjects">
<dt>// key | tr //:</dt><dd>//value//<dd>
<dt ng-if="$index==0"><a href="/project">// key | tr //</a>:</dt><dd ng-if="$index==0">//value//<dd>
<dt ng-if="$index==1">// key | tr //:</dt><dd ng-if="$index==1">//value//<dd>
<dt ng-if="$index==2"><a href="/search?q=">// key | tr //</a>:</dt><dd ng-if="$index==2">//value//<dd>
<dt ng-if="$index>2">// key | tr //:</dt><dd ng-if="$index>2">//value//<dd>
</dl>

View File

@ -53,7 +53,7 @@
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary" id="btnOk" ng-click="form.$valid && vm.save(destination)">// 'ok' | tr //</button>
<button ng-show="vm.editable" type="submit" class="btn btn-primary" id="btnOk" ng-click="vm.save(destination)">// 'ok' | tr //</button>
<button type="button" class="btn btn-default" data-dismiss="modal">// 'close' | tr //</button>
</div>
</div><!-- /.modal-content -->

View File

@ -28,7 +28,7 @@
<table class="table table-pane">
<tbody>
<tr ng-if="vm.replications.length == 0">
<td colspan="7" height="100%" class="empty-hint" ><h3 class="text-muted">// 'no_replications' | tr //</h3></td>
<td colspan="7" height="100%" class="empty-hint" ><h3 class="text-muted">// 'no_replication_policies' | tr //</h3></td>
</tr>
<tr ng-if="vm.replications.length > 0" ng-repeat="r in vm.replications">
<td><a href="repository#/replication?project_id=//r.project_id//">//r.name//</a></td>

View File

@ -1,6 +1,6 @@
<h4 class="page-header title-color underlined">// 'popular_repositories' | tr //</h4>
<div class="col-xs-4 col-md-12 up-table-pane">
<div class="table-head-container" style="width: 650px">
<div class="table-head-container">
<table class="table table-pane table-header">
<thead>
<th width="60%">// 'repository_name' | tr //</th>
@ -9,7 +9,7 @@
</thead>
</table>
</div>
<div class="table-body-container" style="height: 120px; width: 650px">
<div class="table-body-container" ng-style="vm.customBodyHeight">
<table class="table table-pane">
<tbody>
<tr>

View File

@ -30,7 +30,9 @@
'restrict': 'E',
'templateUrl': '/static/resources/js/components/top-repository/top-repository.directive.html',
'controller': TopRepositoryController,
'scope' : true,
'scope' : {
'customBodyHeight': '='
},
'controllerAs': 'vm',
'bindToController': true
};

View File

@ -9,7 +9,7 @@
</thead>
</table>
</div>
<div class="table-body-container">
<div class="table-body-container" style="height: 220px;">
<table class="table table-pane">
<tbody>
<tr>

View File

@ -9,7 +9,8 @@
DashboardController.$inject = ['$scope'];
function DashboardController($scope) {
var vm = this;
vm.customBodyHeight = {'height': '165px'};
}
})();

View File

@ -14,8 +14,11 @@
$scope.subsSection = 32;
$scope.subsSubPane = 226;
var vm = this;
vm.customBodyHeight = {'height': '180px'};
var indexDesc = $filter('tr')('index_desc', []);
var indexDesc1 = $filter('tr')('index_desc_1', []);
var indexDesc2 = $filter('tr')('index_desc_2', []);

View File

@ -154,8 +154,8 @@ var locale_messages = {
'logs' : 'Logs',
'enabled': 'Enabled',
'disabled': 'Disabled',
'no_replication_policies': 'No replication policies, add new replication policy.',
'no_replications': 'No replications.',
'no_replication_policies_add_new': 'No replication policies, add new replication policy.',
'no_replication_policies': 'No replication policies.',
'no_replication_jobs': 'No replication jobs.',
'no_destinations': 'No destinations, add new destination.',
'name_is_required': 'Name is required.',
@ -200,5 +200,13 @@ var locale_messages = {
'delete_repo': 'Delete Repo',
'download_log': 'Download Logs',
'edit': 'Edit',
'delete': 'Delete'
'delete': 'Delete',
'transfer': 'Transfer',
'all': 'All',
'pending': 'Pending',
'running': 'Running',
'finished': 'Finished',
'canceled': 'Canceled',
'stopped': 'Stopped',
'error': 'Error'
};

View File

@ -152,7 +152,8 @@ var locale_messages = {
'logs': '日志',
'enabled': '启用',
'disabled': '停用',
'no_replication_policies': '没有复制策略,请新增复制策略。',
'no_replication_policies_add_new': '没有复制策略,请新增复制策略。',
'no_replication_policies': '没有复制策略。',
'no_replications': '没有复制策略。',
'no_replication_jobs': '没有复制任务。',
'no_destinations': '没有目标设置,请新增目标。',
@ -199,5 +200,13 @@ var locale_messages = {
'delete_repo': '删除镜像仓库',
'download_log': '下载日志',
'edit': '修改',
'delete': '删除'
'delete': '删除',
'all': '全部',
'transfer': '传输',
'pending': '挂起',
'running': '进行中',
'finished': '已完成',
'canceled': '取消',
'stopped': '停止',
'error': '错误'
};

View File

@ -12,12 +12,13 @@
return listReplicationJob;
function listReplicationJob(policyId, repository) {
function listReplicationJob(policyId, repository, status) {
return $http
.get('/api/jobs/replication/', {
'params': {
'policy_id': policyId,
'repository': repository
'repository': repository,
'status': status
}
});
}

View File

@ -10,7 +10,7 @@
</div>
<div class="col-xs-8 col-md-8">
<div class="up-section">
<top-repository></top-repository>
<top-repository custom-body-height="vm.customBodyHeight"></top-repository>
</div>
</div>
</div>
@ -20,7 +20,7 @@
<div class="col-xs-12 col-md-12">
<div class="down-section">
<h4 class="page-header title-color underlined">// 'logs' | tr //</h4>
<user-log></user-log>
<user-log></user-log>
</div>
</div>
</div>

View File

@ -52,7 +52,7 @@
</div>
<div class="col-md-8">
<div class="down-section">
<top-repository></top-repository>
<top-repository custom-body-height="vm.customBodyHeight"></top-repository>
</div>
</div>
</div>