mirror of
https://github.com/ansible/awx.git
synced 2026-01-15 20:00:43 -03:30
Merge branch 'devel' of github.com:ansible/ansible-tower into merge-devel
This commit is contained in:
commit
161f4f22cf
@ -1,5 +1,10 @@
|
||||
import pytest
|
||||
|
||||
from django.core.urlresolvers import resolve
|
||||
from django.utils.six.moves.urllib.parse import urlparse
|
||||
|
||||
from awx.main.models.organization import Organization
|
||||
from awx.main.models.ha import Instance
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from rest_framework.test import (
|
||||
@ -105,13 +110,16 @@ def permissions():
|
||||
|
||||
@pytest.fixture
|
||||
def post():
|
||||
def rf(_cls, _user, _url, pk=None, kwargs={}, middleware=None):
|
||||
view = _cls.as_view()
|
||||
request = APIRequestFactory().post(_url, kwargs, format='json')
|
||||
def rf(url, data, user=None, middleware=None, **kwargs):
|
||||
view, view_args, view_kwargs = resolve(urlparse(url)[2])
|
||||
if 'format' not in kwargs:
|
||||
kwargs['format'] = 'json'
|
||||
request = APIRequestFactory().post(url, data, **kwargs)
|
||||
if middleware:
|
||||
middleware.process_request(request)
|
||||
force_authenticate(request, user=_user)
|
||||
response = view(request, pk=pk)
|
||||
if user:
|
||||
force_authenticate(request, user=user)
|
||||
response = view(request, *view_args, **view_kwargs)
|
||||
if middleware:
|
||||
middleware.process_response(request, response)
|
||||
return response
|
||||
@ -119,13 +127,101 @@ def post():
|
||||
|
||||
@pytest.fixture
|
||||
def get():
|
||||
def rf(_cls, _user, _url, pk=None, middleware=None):
|
||||
view = _cls.as_view()
|
||||
request = APIRequestFactory().get(_url, format='json')
|
||||
def rf(url, user=None, middleware=None, **kwargs):
|
||||
view, view_args, view_kwargs = resolve(urlparse(url)[2])
|
||||
if 'format' not in kwargs:
|
||||
kwargs['format'] = 'json'
|
||||
request = APIRequestFactory().get(url, **kwargs)
|
||||
if middleware:
|
||||
middleware.process_request(request)
|
||||
force_authenticate(request, user=_user)
|
||||
response = view(request, pk=pk)
|
||||
if user:
|
||||
force_authenticate(request, user=user)
|
||||
response = view(request, *view_args, **view_kwargs)
|
||||
if middleware:
|
||||
middleware.process_response(request, response)
|
||||
return response
|
||||
return rf
|
||||
|
||||
@pytest.fixture
|
||||
def put():
|
||||
def rf(url, data, user=None, middleware=None, **kwargs):
|
||||
view, view_args, view_kwargs = resolve(urlparse(url)[2])
|
||||
if 'format' not in kwargs:
|
||||
kwargs['format'] = 'json'
|
||||
request = APIRequestFactory().put(url, data, **kwargs)
|
||||
if middleware:
|
||||
middleware.process_request(request)
|
||||
if user:
|
||||
force_authenticate(request, user=user)
|
||||
response = view(request, *view_args, **view_kwargs)
|
||||
if middleware:
|
||||
middleware.process_response(request, response)
|
||||
return response
|
||||
return rf
|
||||
|
||||
@pytest.fixture
|
||||
def patch():
|
||||
def rf(url, data, user=None, middleware=None, **kwargs):
|
||||
view, view_args, view_kwargs = resolve(urlparse(url)[2])
|
||||
if 'format' not in kwargs:
|
||||
kwargs['format'] = 'json'
|
||||
request = APIRequestFactory().patch(url, data, **kwargs)
|
||||
if middleware:
|
||||
middleware.process_request(request)
|
||||
if user:
|
||||
force_authenticate(request, user=user)
|
||||
response = view(request, *view_args, **view_kwargs)
|
||||
if middleware:
|
||||
middleware.process_response(request, response)
|
||||
return response
|
||||
return rf
|
||||
|
||||
@pytest.fixture
|
||||
def delete():
|
||||
def rf(url, user=None, middleware=None, **kwargs):
|
||||
view, view_args, view_kwargs = resolve(urlparse(url)[2])
|
||||
if 'format' not in kwargs:
|
||||
kwargs['format'] = 'json'
|
||||
request = APIRequestFactory().delete(url, **kwargs)
|
||||
if middleware:
|
||||
middleware.process_request(request)
|
||||
if user:
|
||||
force_authenticate(request, user=user)
|
||||
response = view(request, *view_args, **view_kwargs)
|
||||
if middleware:
|
||||
middleware.process_response(request, response)
|
||||
return response
|
||||
return rf
|
||||
|
||||
@pytest.fixture
|
||||
def head():
|
||||
def rf(url, user=None, middleware=None, **kwargs):
|
||||
view, view_args, view_kwargs = resolve(urlparse(url)[2])
|
||||
if 'format' not in kwargs:
|
||||
kwargs['format'] = 'json'
|
||||
request = APIRequestFactory().head(url, **kwargs)
|
||||
if middleware:
|
||||
middleware.process_request(request)
|
||||
if user:
|
||||
force_authenticate(request, user=user)
|
||||
response = view(request, *view_args, **view_kwargs)
|
||||
if middleware:
|
||||
middleware.process_response(request, response)
|
||||
return response
|
||||
return rf
|
||||
|
||||
@pytest.fixture
|
||||
def options():
|
||||
def rf(url, data, user=None, middleware=None, **kwargs):
|
||||
view, view_args, view_kwargs = resolve(urlparse(url)[2])
|
||||
if 'format' not in kwargs:
|
||||
kwargs['format'] = 'json'
|
||||
request = APIRequestFactory().options(url, data, **kwargs)
|
||||
if middleware:
|
||||
middleware.process_request(request)
|
||||
if user:
|
||||
force_authenticate(request, user=user)
|
||||
response = view(request, *view_args, **view_kwargs)
|
||||
if middleware:
|
||||
middleware.process_response(request, response)
|
||||
return response
|
||||
|
||||
@ -1,11 +1,6 @@
|
||||
import mock
|
||||
import pytest
|
||||
|
||||
from awx.api.views import (
|
||||
ActivityStreamList,
|
||||
ActivityStreamDetail,
|
||||
OrganizationList,
|
||||
)
|
||||
from awx.main.middleware import ActivityStreamMiddleware
|
||||
from awx.main.models.activity_stream import ActivityStream
|
||||
from django.core.urlresolvers import reverse
|
||||
@ -17,7 +12,7 @@ def mock_feature_enabled(feature, bypass_database=None):
|
||||
@pytest.mark.django_db
|
||||
def test_get_activity_stream_list(monkeypatch, organization, get, user):
|
||||
url = reverse('api:activity_stream_list')
|
||||
response = get(ActivityStreamList, user('admin', True), url)
|
||||
response = get(url, user('admin', True))
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
@ -31,7 +26,7 @@ def test_basic_fields(monkeypatch, organization, get, user):
|
||||
|
||||
aspk = activity_stream.pk
|
||||
url = reverse('api:activity_stream_detail', args=(aspk,))
|
||||
response = get(ActivityStreamDetail, user('admin', True), url, pk=aspk)
|
||||
response = get(url, user('admin', True))
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'related' in response.data
|
||||
@ -46,8 +41,9 @@ def test_middleware_actor_added(monkeypatch, post, get, user):
|
||||
u = user('admin-poster', True)
|
||||
|
||||
url = reverse('api:organization_list')
|
||||
response = post(OrganizationList, u, url,
|
||||
kwargs=dict(name='test-org', description='test-desc'),
|
||||
response = post(url,
|
||||
dict(name='test-org', description='test-desc'),
|
||||
u,
|
||||
middleware=ActivityStreamMiddleware())
|
||||
assert response.status_code == 201
|
||||
|
||||
@ -55,7 +51,7 @@ def test_middleware_actor_added(monkeypatch, post, get, user):
|
||||
activity_stream = ActivityStream.objects.filter(organization__pk=org_id).first()
|
||||
|
||||
url = reverse('api:activity_stream_detail', args=(activity_stream.pk,))
|
||||
response = get(ActivityStreamDetail, u, url, pk=activity_stream.pk)
|
||||
response = get(url, u)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.data['summary_fields']['actor']['username'] == 'admin-poster'
|
||||
|
||||
@ -549,7 +549,7 @@
|
||||
word-break: break-all;
|
||||
word-wrap: break-word;
|
||||
padding: 9.5px;
|
||||
ont-family: Fixed, monospace;
|
||||
font-family: Fixed, monospace;
|
||||
max-height: 200px;
|
||||
}
|
||||
|
||||
|
||||
@ -31,6 +31,7 @@ import systemTracking from './system-tracking/main';
|
||||
import inventoryScripts from './inventory-scripts/main';
|
||||
import permissions from './permissions/main';
|
||||
import managementJobs from './management-jobs/main';
|
||||
import jobDetail from './job-detail/main';
|
||||
|
||||
// modules
|
||||
import setupMenu from './setup-menu/main';
|
||||
@ -43,8 +44,7 @@ import templateUrl from './shared/template-url/main';
|
||||
import adhoc from './adhoc/main';
|
||||
import login from './login/main';
|
||||
import activityStream from './activity-stream/main';
|
||||
import {JobDetailController} from './controllers/JobDetail';
|
||||
import {JobStdoutController} from './controllers/JobStdout';
|
||||
import standardOut from './standard-out/main';
|
||||
import {JobTemplatesList, JobTemplatesAdd, JobTemplatesEdit} from './controllers/JobTemplates';
|
||||
import {LicenseController} from './controllers/License';
|
||||
import {ScheduleEditController} from './controllers/Schedules';
|
||||
@ -95,6 +95,8 @@ var tower = angular.module('Tower', [
|
||||
login.name,
|
||||
activityStream.name,
|
||||
footer.name,
|
||||
jobDetail.name,
|
||||
standardOut.name,
|
||||
'templates',
|
||||
'Utilities',
|
||||
'LicenseHelper',
|
||||
@ -293,83 +295,6 @@ var tower = angular.module('Tower', [
|
||||
}
|
||||
}).
|
||||
|
||||
state('jobDetail', {
|
||||
url: '/jobs/:id',
|
||||
templateUrl: urlPrefix + 'partials/job_detail.html',
|
||||
controller: JobDetailController,
|
||||
ncyBreadcrumb: {
|
||||
parent: 'jobs',
|
||||
label: "{{ job.id }} - {{ job.name }}"
|
||||
},
|
||||
resolve: {
|
||||
features: ['FeaturesService', function(FeaturesService) {
|
||||
return FeaturesService.get();
|
||||
}],
|
||||
jobEventsSocket: ['Socket', '$rootScope', function(Socket, $rootScope) {
|
||||
if (!$rootScope.event_socket) {
|
||||
$rootScope.event_socket = Socket({
|
||||
scope: $rootScope,
|
||||
endpoint: "job_events"
|
||||
});
|
||||
$rootScope.event_socket.init();
|
||||
return true;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}]
|
||||
}
|
||||
}).
|
||||
|
||||
state('jobsStdout', {
|
||||
url: '/jobs/:id/stdout',
|
||||
templateUrl: urlPrefix + 'partials/job_stdout.html',
|
||||
controller: JobStdoutController,
|
||||
ncyBreadcrumb: {
|
||||
parent: 'jobDetail',
|
||||
label: "STANDARD OUT"
|
||||
},
|
||||
resolve: {
|
||||
features: ['FeaturesService', function(FeaturesService) {
|
||||
return FeaturesService.get();
|
||||
}],
|
||||
jobEventsSocket: ['Socket', '$rootScope', function(Socket, $rootScope) {
|
||||
if (!$rootScope.event_socket) {
|
||||
$rootScope.event_socket = Socket({
|
||||
scope: $rootScope,
|
||||
endpoint: "job_events"
|
||||
});
|
||||
$rootScope.event_socket.init();
|
||||
return true;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}]
|
||||
}
|
||||
}).
|
||||
|
||||
state('adHocJobStdout', {
|
||||
url: '/ad_hoc_commands/:id',
|
||||
templateUrl: urlPrefix + 'partials/job_stdout_adhoc.html',
|
||||
controller: JobStdoutController,
|
||||
resolve: {
|
||||
features: ['FeaturesService', function(FeaturesService) {
|
||||
return FeaturesService.get();
|
||||
}],
|
||||
adhocEventsSocket: ['Socket', '$rootScope', function(Socket, $rootScope) {
|
||||
if (!$rootScope.adhoc_event_socket) {
|
||||
$rootScope.adhoc_event_socket = Socket({
|
||||
scope: $rootScope,
|
||||
endpoint: "ad_hoc_command_events"
|
||||
});
|
||||
$rootScope.adhoc_event_socket.init();
|
||||
return true;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}]
|
||||
}
|
||||
}).
|
||||
|
||||
state('jobTemplates', {
|
||||
url: '/job_templates',
|
||||
templateUrl: urlPrefix + 'partials/job_templates.html',
|
||||
@ -1062,6 +987,9 @@ var tower = angular.module('Tower', [
|
||||
$rootScope.$emit('JobStatusChange-jobs', data);
|
||||
} else if (/\/jobs\/(\d)+\/stdout/.test(urlToCheck) ||
|
||||
/\/ad_hoc_commands\/(\d)+/.test(urlToCheck)) {
|
||||
|
||||
// TODO: something will need to change here for stdout
|
||||
|
||||
$log.debug("sending status to standard out");
|
||||
$rootScope.$emit('JobStatusChange-jobStdout', data);
|
||||
} else if (/\/jobs\/(\d)+/.test(urlToCheck)) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -22,12 +22,11 @@ export default
|
||||
* Initialize calling scope with all the bits required to support a jobs list
|
||||
*
|
||||
*/
|
||||
.factory('JobsControllerInit', ['$location', 'Find', 'DeleteJob', 'RelaunchJob', 'LogViewer', '$window',
|
||||
function($location, Find, DeleteJob, RelaunchJob, LogViewer, $window) {
|
||||
.factory('JobsControllerInit', ['$state', 'Find', 'DeleteJob', 'RelaunchJob', 'LogViewer', '$window',
|
||||
function($state, Find, DeleteJob, RelaunchJob, LogViewer, $window) {
|
||||
return function(params) {
|
||||
var scope = params.scope,
|
||||
iterator = (params.iterator) ? params.iterator : scope.iterator;
|
||||
//base = $location.path().replace(/^\//, '').split('/')[0];
|
||||
|
||||
scope.deleteJob = function(id) {
|
||||
DeleteJob({ scope: scope, id: id });
|
||||
@ -70,53 +69,39 @@ export default
|
||||
};
|
||||
|
||||
scope.refreshJobs = function() {
|
||||
// if (base !== 'jobs') {
|
||||
scope.search(iterator);
|
||||
// }
|
||||
|
||||
scope.search(iterator);
|
||||
};
|
||||
|
||||
scope.viewJobLog = function(id) {
|
||||
var list, job;
|
||||
if (scope.completed_jobs) {
|
||||
list = scope.completed_jobs;
|
||||
}
|
||||
else if (scope.running_jobs) {
|
||||
list = scope.running_jobs;
|
||||
}
|
||||
else if (scope.queued_jobs) {
|
||||
list = scope.queued_jobs;
|
||||
}
|
||||
else if (scope.jobs) {
|
||||
list = scope.jobs;
|
||||
}
|
||||
else if(scope.all_jobs){
|
||||
list = scope.all_jobs;
|
||||
}
|
||||
else if(scope.portal_jobs){
|
||||
list=scope.portal_jobs;
|
||||
}
|
||||
job = Find({ list: list, key: 'id', val: id });
|
||||
if (job.type === 'job') {
|
||||
scope.viewJobDetails = function(job) {
|
||||
|
||||
var goToJobDetails = function(state) {
|
||||
if(scope.$parent.portalMode===true){
|
||||
$window.open('/#/jobs/' + job.id, '_blank');
|
||||
var url = $state.href(state, {id: job.id});
|
||||
$window.open(url, '_blank');
|
||||
}
|
||||
else {
|
||||
$location.url('/jobs/' + job.id);
|
||||
$state.go(state, {id: job.id});
|
||||
}
|
||||
} else if (job.type === 'ad_hoc_command') {
|
||||
if(scope.$parent.portalMode===true){
|
||||
$window.open('/#/ad_hoc_commands/' + job.id, '_blank');
|
||||
}
|
||||
else {
|
||||
$location.url('/ad_hoc_commands/' + job.id);
|
||||
}
|
||||
} else {
|
||||
LogViewer({
|
||||
scope: scope,
|
||||
url: job.url
|
||||
});
|
||||
}
|
||||
|
||||
switch(job.type) {
|
||||
case 'job':
|
||||
goToJobDetails('jobDetail');
|
||||
break;
|
||||
case 'ad_hoc_command':
|
||||
goToJobDetails('adHocJobStdout');
|
||||
break;
|
||||
case 'system_job':
|
||||
goToJobDetails('managementJobStdout');
|
||||
break;
|
||||
case 'project_update':
|
||||
goToJobDetails('scmUpdateStdout');
|
||||
break;
|
||||
case 'inventory_update':
|
||||
goToJobDetails('inventorySyncStdout');
|
||||
break;
|
||||
}
|
||||
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
58
awx/ui/client/src/job-detail/job-detail.block.less
Normal file
58
awx/ui/client/src/job-detail/job-detail.block.less
Normal file
@ -0,0 +1,58 @@
|
||||
/** @define SetupItem */
|
||||
|
||||
@import '../shared/branding/colors.less';
|
||||
@import '../shared/branding/colors.default.less';
|
||||
|
||||
.JobDetail-panelHeader{
|
||||
height: 50px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.JobDetail-panelHeaderText{
|
||||
color: @default-interface-txt;
|
||||
flex: 1 0 auto;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
margin-right: 10px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.JobDetail-panelHeaderText:hover{
|
||||
color: @default-interface-txt;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
margin-right: 10px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.JobDetail-expandArrow{
|
||||
color: @default-icon-hov;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
margin-right: 10px;
|
||||
text-transform: uppercase;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.JobDetail-resultsDetails{
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.JobDetail-resultRow{
|
||||
width: 50%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.JobDetail-resultRow label{
|
||||
color: @default-interface-txt;
|
||||
font-size: 14px;
|
||||
font-weight: normal!important;
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
|
||||
.JobDetail-resultRowText{
|
||||
width: 40%;
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
1452
awx/ui/client/src/job-detail/job-detail.controller.js
Normal file
1452
awx/ui/client/src/job-detail/job-detail.controller.js
Normal file
File diff suppressed because it is too large
Load Diff
0
awx/ui/client/src/job-detail/job-detail.factory.js
Normal file
0
awx/ui/client/src/job-detail/job-detail.factory.js
Normal file
@ -1,29 +1,25 @@
|
||||
<div class="tab-pane" id="jobs-detail">
|
||||
<div ng-cloak id="htmlTemplate">
|
||||
|
||||
<div class="row" style="position: relative;">
|
||||
<div id="job-detail-container">
|
||||
<div class="job_well">
|
||||
<div class="row">
|
||||
<label class="col-lg-2 col-md-2 col-sm-2 col-xs-3">Status</label>
|
||||
<div class="col-lg-6 col-md-6 col-sm-6 col-xs-5 job_status"><i class="fa icon-job-{{ job_status.status }}"></i> {{ job_status.status_label }}</div>
|
||||
|
||||
<div class="col-lg-4 text-right JobDetails-status">
|
||||
<a href="" id="play-help" aw-pop-over="Live event processing is now paused. Click here to resume." id="play-button-help" data-placement="left" ng-show="pauseLiveEvents" ><i class="fa fa-question"></i></a>
|
||||
<a href="" ng-click="togglePlayButton()" id="play-button" class="btn btn-primary btn-xs" aw-tool-tip="Resume viewing live events" data-placement="top" ng-show="pauseLiveEvents" style="margin-right:25px;"><i class="fa fa-play"></i></a>
|
||||
<a href="/#/jobs/{{ job_id }}/stdout" id="view-stdout-button" target="_blank" type="button" class="btn btn-primary btn-xs" aw-tool-tip="View standard out. Opens in new tab or window." data-placement="top"><i class="fa fa-external-link"></i></a>
|
||||
<button type="button" class="btn btn-xs btn-primary ng-hide" ng-click="refresh()" id="refresh_btn" aw-tool-tip="Refresh the page" data-placement="top" ng-show="socketStatus == 'error'"
|
||||
data-original-title="" title=""><i class="fa fa-refresh"></i></button>
|
||||
<a href="" ng-click="deleteJob()" id="cancel-job-button" ng-show="job_status.status == 'running' || job_status.status=='pending' " type="button" class="btn btn-primary btn-xs" aw-tool-tip="Cancel" data-placement="top"><i class="fa fa-minus-circle"></i></a>
|
||||
<a href="" ng-click="deleteJob()" id="delete-job-button" ng-hide="job_status.status == 'running' || job_status.status == 'pending' " type="button" class="btn btn-primary btn-xs" aw-tool-tip="Delete" data-placement="top"><i class="fa fa-trash-o"></i></a>
|
||||
<a href="" ng-click="relaunchJob()" id="relaunch-job-button" type="button" class="btn btn-primary btn-xs" aw-tool-tip="Relaunch using the same parameters" data-placement="top"><i class="fa fa-rocket"></i></a>
|
||||
<button type="button" id="summary-button" class="btn btn-primary btn-xs" ng-click="toggleSummary()" aw-tool-tip="View summary" data-placement="top"><i class="fa fa-arrow-circle-left"></i></button>
|
||||
</div>
|
||||
<div class="JobDetail-resultsContainer Panel">
|
||||
<div class="JobDetail-panelHeader">
|
||||
<a class="JobDetail-panelHeaderText" ng-show="lessStatus" href="" ng-click="toggleLessStatus()">
|
||||
RESULTS<i class="JobDetail-expandArrow fa fa-caret-left"></i>
|
||||
</a>
|
||||
<a class="JobDetail-panelHeaderText" ng-show="!lessStatus" href="" ng-click="toggleLessStatus()">
|
||||
RESULTS<i class="JobDetail-expandArrow fa fa-caret-down"></i>
|
||||
</a>
|
||||
<button id="submit-action" class="List-actionButton JobDetail-launchButton" data-placement="top" mode="all" ng-click="relaunchJob()" aw-tool-tip="Start a job using this template" data-original-title="" title=""><i class="fa fa-rocket"></i> </button>
|
||||
<button id="delete-action" class="List-actionButton List-actionButton--delete JobDetail-launchButton" data-placement="top" ng-click="deleteJobTemplate(job_template.id, job_template.name)" aw-tool-tip="Delete template" data-original-title="" title=""><i class="fa fa-trash-o"></i> </button>
|
||||
</div>
|
||||
|
||||
<div class="form-horizontal" role="form" id="job-status-form">
|
||||
|
||||
<div class="form-group" ng-show="job_status.explanation">
|
||||
<div class="form-horizontal JobDetail-resultsDetails" role="form" id="job-status-form">
|
||||
<div class="form-group JobDetail-resultRow toggle-show" ng-show="job_status.started">
|
||||
<label class="col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label">Status</label>
|
||||
<div class="JobDetail-resultRowText"><i class="fa icon-job-{{ job_status.status }}"></i> {{ job_status.status_label }}</div>
|
||||
</div>
|
||||
<div class="form-group JobDetail-resultRow toggle-show" ng-show="job_status.explanation">
|
||||
<label class="col-lg-2 col-md-2 col-sm-2 col-xs-3 col-xs-12">Explanation</label>
|
||||
<div class="col-lg-10 col-md-10 col-sm-10 col-xs-9 job_status_explanation"
|
||||
ng-show="!previousTaskFailed" ng-bind-html="job_status.explanation"></div>
|
||||
@ -44,117 +40,116 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-show="job_status.traceback">
|
||||
<div class="form-group JobDetail-resultRow toggle-show" ng-show="job_status.traceback">
|
||||
<label class="col-lg-2 col-md-12 col-sm-12 col-xs-12">Results Traceback</label>
|
||||
<div class="col-lg-10 col-md-12 col-sm-12 col-xs-12 job_status_traceback" ng-bind-html="job_status.traceback"></div>
|
||||
<div class="JobDetail-resultRowText col-lg-10 col-md-12 col-sm-12 col-xs-12 job_status_traceback" ng-bind-html="job_status.traceback"></div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-show="job_status.started">
|
||||
<label class="col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label">Timing</label>
|
||||
<div class="col-lg-10 col-md-10 col-sm-10 col-xs-9">
|
||||
<div ng-show="job_status.started" id="started-time">Started {{ job_status.started | date:'MM/dd/yy HH:mm:ss' }}</div>
|
||||
<div ng-show="job_status.finished" id="finished-time">Finished {{ job_status.finished | date:'MM/dd/yy HH:mm:ss' }}</div>
|
||||
<div ng-show="job_status.finished" id="elapsed-time">Elapsed {{ job_status.elapsed }}</div>
|
||||
</div>
|
||||
<div class="form-group JobDetail-resultRow toggle-show" ng-show="job_status.started">
|
||||
<label class="col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label">Started</label>
|
||||
<div class="JobDetail-resultRowText">{{ job_status.started | date:'MM/dd/yy HH:mm:ss' }}</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group toggle-show" style="display:none;" ng-show="job_template_name">
|
||||
<div class="form-group JobDetail-resultRow toggle-show" ng-show="job_status.started">
|
||||
<label class="col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label">Finished</label>
|
||||
<div class="JobDetail-resultRowText">{{ job_status.finished | date:'MM/dd/yy HH:mm:ss' }}</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group JobDetail-resultRow toggle-show" ng-show="job_status.started">
|
||||
<label class="col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label">Elapsed</label>
|
||||
<div class="JobDetail-resultRowText">{{ job_status.elapsed }}</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group JobDetail-resultRow toggle-show" ng-show="job_template_name">
|
||||
<label class="col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label">Template</label>
|
||||
<div class="col-lg-10- col-md-10 col-sm-10 col-xs-9">
|
||||
<div class="JobDetail-resultRowText">
|
||||
<a href="{{ job_template_url }}" aw-tool-tip="Edit the job template" data-placement="top">{{ job_template_name }}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group toggle-show" style="display:none;" ng-show="job_type">
|
||||
<div class="form-group JobDetail-resultRow toggle-show" ng-show="job_type">
|
||||
<label class="col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label">Job Type</label>
|
||||
<div class="col-lg-10- col-md-10 col-sm-10 col-xs-9">{{ job_type }}</div>
|
||||
<div class="JobDetail-resultRowText">{{ job_type }}</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group toggle-show" style="display:none;" ng-show="created_by">
|
||||
<div class="form-group JobDetail-resultRow toggle-show" ng-show="created_by">
|
||||
<label class="col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label">Launched By</label>
|
||||
<div class="col-lg-10- col-md-10 col-sm-10 col-xs-9">
|
||||
<div class="JobDetail-resultRowText">
|
||||
<a href="{{ users_url }}" aw-tool-tip="Edit the User" data-placement="top">{{ created_by }}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group toggle-show" style="display:none;" ng-show="scheduled_by">
|
||||
<div class="form-group JobDetail-resultRow toggle-show" ng-show="scheduled_by">
|
||||
<label class="col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label">Launched By</label>
|
||||
<div class="col-lg-10- col-md-10 col-sm-10 col-xs-9">
|
||||
<div class="JobDetail-resultRowText">
|
||||
<a href aw-tool-tip="Edit the Schedule" data-placement="top" ng-click="editSchedule()">{{scheduled_by}}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group toggle-show" style="display:none;" ng-show="inventory_name">
|
||||
<div class="form-group JobDetail-resultRow toggle-show" ng-show="inventory_name">
|
||||
<label class="col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label">Inventory</label>
|
||||
<div class="col-lg-10- col-md-10 col-sm-10 col-xs-9">
|
||||
<div class="JobDetail-resultRowText">
|
||||
<a href="{{ inventory_url }}" aw-tool-tip="Edit the inventory" data-placement="top">{{ inventory_name }}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group toggle-show" style="display:none;" ng-show="project_name">
|
||||
<div class="form-group JobDetail-resultRow toggle-show" ng-show="project_name">
|
||||
<label class="col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label">Project</label>
|
||||
<div class="col-lg-10- col-md-10 col-sm-10 col-xs-9">
|
||||
<div class="JobDetail-resultRowText">
|
||||
<a href="{{ project_url }}" aw-tool-tip="Edit the project" data-placement="top">{{ project_name }}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group toggle-show" style="display:none;" ng-show="job.playbook">
|
||||
<div class="form-group JobDetail-resultRow toggle-show" ng-show="job.playbook">
|
||||
<label class="col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label">Playbook</label>
|
||||
<div class="col-lg-10- col-md-10 col-sm-10 col-xs-9">{{ job.playbook }}</div>
|
||||
<div class="JobDetail-resultRowText">{{ job.playbook }}</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group toggle-show" style="display:none;" ng-show="credential_name">
|
||||
<div class="form-group JobDetail-resultRow toggle-show" ng-show="credential_name">
|
||||
<label class="col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label">Machine Credential</label>
|
||||
<div class="col-lg-10- col-md-10 col-sm-10 col-xs-9">
|
||||
<div class="JobDetail-resultRowText JobDetail-resultRowText">
|
||||
<a href="{{ credential_url }}" aw-tool-tip="Edit the credential" data-placement="top">{{ credential_name }}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group toggle-show" style="display:none;" ng-show="cloud_credential_name">
|
||||
<div class="form-group JobDetail-resultRow toggle-show" ng-show="cloud_credential_name">
|
||||
<label class="col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label">Cloud Credential</label>
|
||||
<div class="col-lg-10- col-md-10 col-sm-10 col-xs-9">
|
||||
<div class="JobDetail-resultRowText">
|
||||
<a href="{{ cloud_credential_url }}" aw-tool-tip="Edit the credential" data-placement="top">{{ cloud_credential_name }}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group toggle-show" style="display:none;" ng-show="job.forks">
|
||||
<div class="form-group JobDetail-resultRow toggle-show" ng-show="job.forks">
|
||||
<label class="col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label">Forks</label>
|
||||
<div class="col-lg-10- col-md-10 col-sm-10 col-xs-9">{{ job.forks }}</div>
|
||||
<div class="JobDetail-resultRowText">{{ job.forks }}</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group toggle-show" style="display:none;" ng-show="job.limit">
|
||||
<div class="form-group JobDetail-resultRow toggle-show" ng-show="job.limit">
|
||||
<label class="col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label">Limit</label>
|
||||
<div class="col-lg-10- col-md-10 col-sm-10 col-xs-9">{{ job.limit }}</div>
|
||||
<div class="JobDetail-resultRowText">{{ job.limit }}</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group toggle-show" style="display:none;" ng-show="verbosity">
|
||||
<div class="form-group JobDetail-resultRow toggle-show" ng-show="verbosity">
|
||||
<label class="col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label">Verbosity</label>
|
||||
<div class="col-lg-10- col-md-10 col-sm-10 col-xs-9">{{ verbosity }}</div>
|
||||
<div class="JobDetail-resultRowText">{{ verbosity }}</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group toggle-show" style="display:none;" ng-show="job.job_tags">
|
||||
<div class="form-group JobDetail-resultRow toggle-show" ng-show="job.job_tags">
|
||||
<label class="col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label">Job Tags</label>
|
||||
<div class="col-lg-10- col-md-10 col-sm-10 col-xs-9">{{ job.job_tags }}</div>
|
||||
<div class="JobDetail-resultRowText">{{ job.job_tags }}</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group toggle-show" style="display:none;" ng-show="variables">
|
||||
<div class="form-group JobDetail-resultRow toggle-show" ng-show="variables">
|
||||
<label class="col-lg-2 col-md-2 col-sm-2 col-xs-3 control-label">Extra Variables</label>
|
||||
<div class="col-lg-10- col-md-10 col-sm-10 col-xs-9">
|
||||
<div class="JobDetail-resultRowText">
|
||||
<div id="pre-formatted-variables">{{ variables }}</div>
|
||||
<!-- <pre>{{ variables }}</pre> -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12 more-or-less">
|
||||
<a ng-show="lessStatus" href="" ng-click="toggleLessStatus()">more <i class="fa fa-angle-down"></i></a>
|
||||
<a ng-show="!lessStatus" href="" ng-click="toggleLessStatus()">less <i class="fa fa-angle-up"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<!--- JobDetail-results---------------------------------------------->
|
||||
|
||||
<div id="job-detail-tables">
|
||||
<div id="play-section" class="section">
|
||||
@ -378,11 +373,11 @@
|
||||
</div><!-- section -->
|
||||
</div><!-- job-detail-tables -->
|
||||
|
||||
</div><!-- well -->
|
||||
|
||||
|
||||
</div><!-- job-detail-container -->
|
||||
|
||||
<div id="job-summary-container">
|
||||
<!-- <div id="job-summary-container"> -->
|
||||
<div class="job_well">
|
||||
<div id="summary-well-top-section">
|
||||
<div id="hide-summary-button" style="display: hidden;">
|
||||
35
awx/ui/client/src/job-detail/job-detail.route.js
Normal file
35
awx/ui/client/src/job-detail/job-detail.route.js
Normal file
@ -0,0 +1,35 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2015 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
import {templateUrl} from '../shared/template-url/template-url.factory';
|
||||
|
||||
export default {
|
||||
name: 'jobDetail',
|
||||
url: '/jobs/:id',
|
||||
templateUrl: templateUrl('job-detail/job-detail'),
|
||||
controller: 'JobDetailController',
|
||||
ncyBreadcrumb: {
|
||||
parent: 'jobs',
|
||||
label: "{{ job.id }} - {{ job.name }}"
|
||||
},
|
||||
resolve: {
|
||||
features: ['FeaturesService', function(FeaturesService) {
|
||||
return FeaturesService.get();
|
||||
}],
|
||||
jobEventsSocket: ['Socket', '$rootScope', function(Socket, $rootScope) {
|
||||
if (!$rootScope.event_socket) {
|
||||
$rootScope.event_socket = Socket({
|
||||
scope: $rootScope,
|
||||
endpoint: "job_events"
|
||||
});
|
||||
$rootScope.event_socket.init();
|
||||
return true;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}]
|
||||
}
|
||||
};
|
||||
15
awx/ui/client/src/job-detail/main.js
Normal file
15
awx/ui/client/src/job-detail/main.js
Normal file
@ -0,0 +1,15 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2016 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
import route from './job-detail.route';
|
||||
import controller from './job-detail.controller';
|
||||
|
||||
export default
|
||||
angular.module('jobDetail', [])
|
||||
.controller('JobDetailController', controller)
|
||||
.run(['$stateExtender', function($stateExtender) {
|
||||
$stateExtender.addState(route);
|
||||
}]);
|
||||
@ -1,34 +0,0 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2015 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
/** @define StandardOutDetails */
|
||||
|
||||
// Some of these are left empty as a helpful measure so that you can see how the new
|
||||
// SuitCSS styling should work. They can be removed once we've done more
|
||||
// SuitCSS
|
||||
|
||||
.StandardOutDetails {
|
||||
}
|
||||
|
||||
.StandardOutDetails-detailRow {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.StandardOutDetails-detailRow--closable {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.StandardOutDetails-detailLabel {
|
||||
}
|
||||
|
||||
.StandardOutDetails-detailContent {
|
||||
}
|
||||
|
||||
.StandardOutDetails-closedToggle {
|
||||
}
|
||||
|
||||
.StandardOutDetails-closedToggleLink {
|
||||
}
|
||||
@ -25,7 +25,7 @@ export default
|
||||
dataTitle: "{{ all_job.status_popover_title }}",
|
||||
icon: 'icon-job-{{ all_job.status }}',
|
||||
iconOnly: true,
|
||||
ngClick:"viewJobLog(all_job.id)",
|
||||
ngClick:"viewJobDetails(all_job)",
|
||||
searchable: true,
|
||||
searchType: 'select',
|
||||
nosort: true,
|
||||
@ -38,7 +38,7 @@ export default
|
||||
},
|
||||
id: {
|
||||
label: 'ID',
|
||||
ngClick:"viewJobLog(all_job.id)",
|
||||
ngClick:"viewJobDetails(all_job)",
|
||||
searchType: 'int',
|
||||
columnClass: 'col-lg-1 col-md-1 col-sm-2 col-xs-2 List-staticColumnAdjacent',
|
||||
awToolTip: "{{ all_job.status_tip }}",
|
||||
@ -47,7 +47,7 @@ export default
|
||||
name: {
|
||||
label: 'Name',
|
||||
columnClass: 'col-lg-3 col-md-3 col-sm-4 col-xs-6',
|
||||
ngClick: "viewJobLog(all_job.id, all_job.nameHref)",
|
||||
ngClick: "viewJobDetails(all_job)",
|
||||
defaultSearchField: true,
|
||||
awToolTip: "{{ all_job.name | sanitize }}",
|
||||
dataPlacement: 'top'
|
||||
@ -87,13 +87,6 @@ export default
|
||||
|
||||
columnClass: 'col-lg-2 col-md-2 col-sm-3 col-xs-4',
|
||||
|
||||
stdout: {
|
||||
mode: 'all',
|
||||
href: '/#/jobs/{{ all_job.id }}/stdout',
|
||||
awToolTip: 'View standard output',
|
||||
dataPlacement: 'top',
|
||||
ngShow: "all_job.type == 'job'"
|
||||
},
|
||||
submit: {
|
||||
icon: 'icon-rocket',
|
||||
mode: 'all',
|
||||
|
||||
@ -27,7 +27,7 @@ export default
|
||||
dataTitle: "{{ completed_job.status_popover_title }}",
|
||||
icon: 'icon-job-{{ completed_job.status }}',
|
||||
iconOnly: true,
|
||||
ngClick:"viewJobLog(completed_job.id)",
|
||||
ngClick:"viewJobDetails(completed_job)",
|
||||
searchable: true,
|
||||
searchType: 'select',
|
||||
nosort: true,
|
||||
@ -40,7 +40,7 @@ export default
|
||||
},
|
||||
id: {
|
||||
label: 'ID',
|
||||
ngClick:"viewJobLog(completed_job.id)",
|
||||
ngClick:"viewJobDetails(completed_job)",
|
||||
searchType: 'int',
|
||||
columnClass: 'col-lg-1 col-md-1 col-sm-2 col-xs-2 List-staticColumnAdjacent',
|
||||
awToolTip: "{{ completed_job.status_tip }}",
|
||||
@ -49,7 +49,7 @@ export default
|
||||
name: {
|
||||
label: 'Name',
|
||||
columnClass: 'col-lg-4 col-md-4 col-sm-4 col-xs-6',
|
||||
ngClick: "viewJobLog(completed_job.id, completed_job.nameHref)",
|
||||
ngClick: "viewJobDetails(completed_job)",
|
||||
defaultSearchField: true,
|
||||
awToolTip: "{{ completed_job.name | sanitize }}",
|
||||
dataPlacement: 'top'
|
||||
@ -89,13 +89,6 @@ export default
|
||||
|
||||
columnClass: 'col-lg-2 col-md-2 col-sm-3 col-xs-4',
|
||||
|
||||
stdout: {
|
||||
mode: 'all',
|
||||
href: '/#/jobs/{{ completed_job.id }}/stdout',
|
||||
awToolTip: 'View standard output',
|
||||
dataPlacement: 'top',
|
||||
ngShow: "completed_job.type == 'job'"
|
||||
},
|
||||
submit: {
|
||||
icon: 'icon-rocket',
|
||||
mode: 'all',
|
||||
@ -110,11 +103,5 @@ export default
|
||||
awToolTip: 'Delete the job',
|
||||
dataPlacement: 'top'
|
||||
}
|
||||
// job_details: {
|
||||
// mode: 'all',
|
||||
// ngClick: "viewJobLog(completed_job.id)",
|
||||
// awToolTip: 'View job details',
|
||||
// dataPlacement: 'top'
|
||||
// }
|
||||
}
|
||||
});
|
||||
|
||||
@ -20,7 +20,7 @@ export default
|
||||
fields: {
|
||||
id: {
|
||||
label: 'ID',
|
||||
ngClick:"viewJobLog(job.id)",
|
||||
ngClick:"viewJobDetails(job)",
|
||||
key: true,
|
||||
desc: true,
|
||||
searchType: 'int',
|
||||
@ -36,7 +36,7 @@ export default
|
||||
dataTitle: "{{ job.status_popover_title }}",
|
||||
icon: 'icon-job-{{ job.status }}',
|
||||
iconOnly: true,
|
||||
ngClick:"viewJobLog(job.id)",
|
||||
ngClick:"viewJobDetails(job)",
|
||||
searchable: true,
|
||||
nosort: true,
|
||||
searchType: 'select',
|
||||
@ -66,7 +66,7 @@ export default
|
||||
name: {
|
||||
label: 'Name',
|
||||
columnClass: 'col-md-3 col-xs-5',
|
||||
ngClick: "viewJobLog(job.id, job.nameHref)",
|
||||
ngClick: "viewJobDetails(job)",
|
||||
defaultSearchField: true
|
||||
}
|
||||
},
|
||||
@ -74,13 +74,6 @@ export default
|
||||
actions: { },
|
||||
|
||||
fieldActions: {
|
||||
stdout: {
|
||||
mode: 'all',
|
||||
href: '/#/jobs/{{ job.id }}/stdout',
|
||||
awToolTip: 'View standard output',
|
||||
dataPlacement: 'top',
|
||||
ngShow: "job.type == 'job'"
|
||||
},
|
||||
submit: {
|
||||
mode: 'all',
|
||||
icon: 'icon-rocket',
|
||||
|
||||
@ -64,7 +64,7 @@ export default
|
||||
|
||||
job_details: {
|
||||
mode: 'all',
|
||||
ngClick: "viewJobLog(portal_job.id)",
|
||||
ngClick: "viewJobDetails(portal_job)",
|
||||
awToolTip: 'View job details',
|
||||
dataPlacement: 'top'
|
||||
}
|
||||
|
||||
@ -1,38 +0,0 @@
|
||||
<div class="tab-pane" id="jobs-stdout">
|
||||
<div ng-cloak id="htmlTemplate">
|
||||
|
||||
<div class="StandardOut">
|
||||
<div class="StandardOut-heading">
|
||||
|
||||
<div class="row StandardOut-breadcrumbs">
|
||||
<div id="home-list-actions" class="list-actions pull-right col-md-12">
|
||||
<button type="button" class="btn btn-xs btn-primary ng-hide" ng-click="refresh()" id="refresh_btn" aw-tool-tip="Refresh the page" data-placement="top" ng-show="socketStatus == 'error'" data-original-title="" title=""><i class="fa fa-refresh fa-lg"></i> </button></div>
|
||||
</div>
|
||||
<div class="row StandardOut-form">
|
||||
<div class="col-md-12">
|
||||
<div id="job-status"><label>Job Status</label> <i class="fa icon-job-{{ job.status }}"></i> {{ job.status }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default job-stdout-panel StandardOut-panel">
|
||||
<div class="panel-heading StandardOut-panelHeading">
|
||||
<h3 class="panel-title">Standard Output
|
||||
<a href="/api/v1/jobs/{{ job.id }}/stdout?format=txt_download&token={{ token }}" class="btn btn-primary btn-xs DownloadStandardOut DownloadStandardOut--onStandardOutPage" id="download-stdout-button" type="button" aw-tool-tip="Download standard out as a .txt file" data-placement="top" ng-show="job.status === 'cancelled' || job.status === 'failed' || job.status === 'error' || job.status === 'successful'">
|
||||
<i class="fa fa-download DownloadStandardOut-icon DownloadStandardOut-icon--withText"></i>Download
|
||||
</a>
|
||||
</div>
|
||||
<div class="panel-body stdout-panel-body StandardOut-panelBody">
|
||||
<div id="pre-container" class="body_background
|
||||
body_foreground pre mono-space StandardOut-preContainer"
|
||||
lr-infinite-scroll="stdOutScrollToTop"
|
||||
scroll-threshold="300" data-direction="up" time-threshold="500">
|
||||
<div id="pre-container-content" class="StandardOut-preContent"></div>
|
||||
</div>
|
||||
<div class="scroll-spinner" id="stdoutMoreRowsBottom">
|
||||
<i class="fa fa-cog fa-spin"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1,172 +0,0 @@
|
||||
<div class="tab-pane" id="jobs-stdout">
|
||||
<div ng-cloak id="htmlTemplate">
|
||||
<div class="StandardOut">
|
||||
<div class="row StandardOut-heading">
|
||||
<div class="row StandardOut-breadcrumbs">
|
||||
<div id="home-list-actions"
|
||||
class="list-actions pull-right col-md-12">
|
||||
<button type="button" class="btn btn-xs btn-primary ng-hide"
|
||||
ng-click="refresh()" id="refresh_btn"
|
||||
aw-tool-tip="Refresh the page"
|
||||
data-placement="top" ng-show="socketStatus == 'error'"
|
||||
data-original-title="" title="">
|
||||
<i class="fa fa-refresh fa-lg"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-form form-horizontal StandardOutDetails"
|
||||
role="form" id="job-status-form">
|
||||
<div class="form-group StandardOutDetails-detailRow">
|
||||
<label class="col-lg-2 col-md-2 col-sm-2 col-xs-12
|
||||
StandardOutDetails-detailLabel">Status</label>
|
||||
<div class="col-lg-10 col-md-10 col-sm-10 col-xs-12
|
||||
StandardOutDetails-detailContent">
|
||||
<i class="fa icon-job-{{ job.status }}"></i> {{ job.status }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div <div class="form-group StandardOutDetails-detailRow
|
||||
StandardOutDetails-detailRow--closable"
|
||||
ng-show="job.started">
|
||||
<label class="col-lg-2 col-md-2 col-sm-2 col-xs-12
|
||||
StandardOutDetails-detailLabel">Timing</label>
|
||||
<div class="col-lg-10 col-md-10 col-sm-10 col-xs-12
|
||||
StandardOutDetails-detailContent">
|
||||
<div ng-show="job.started" id="started-time">
|
||||
Started {{ job.started | date:'MM/dd/yy HH:mm:ss' }}
|
||||
</div>
|
||||
<div ng-show="job.finished" id="finished-time">
|
||||
Finished {{ job.finished | date:'MM/dd/yy HH:mm:ss' }}
|
||||
</div>
|
||||
<div ng-show="job.finished" id="elapsed-time">
|
||||
Elapsed {{ job.elapsed }} seconds
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div <div class="form-group StandardOutDetails-detailRow
|
||||
StandardOutDetails-detailRow--closable"
|
||||
ng-show="job.module_name">
|
||||
<label class="col-lg-2 col-md-2 col-sm-2 col-xs-12
|
||||
StandardOutDetails-detailLabel">Module Name</label>
|
||||
<div class="col-lg-10 col-md-10 col-sm-10 col-xs-12
|
||||
StandardOutDetails-detailContent">{{ job.module_name }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div <div class="form-group StandardOutDetails-detailRow
|
||||
StandardOutDetails-detailRow--closable"
|
||||
ng-show="job.module_args">
|
||||
<label class="col-lg-2 col-md-2 col-sm-2 col-xs-12
|
||||
StandardOutDetails-detailLabel">Module Args</label>
|
||||
<div class="col-lg-10 col-md-10 col-sm-10 col-xs-12
|
||||
StandardOutDetails-detailContent mono-space">{{ job.module_args }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div <div class="form-group StandardOutDetails-detailRow
|
||||
StandardOutDetails-detailRow--closable"
|
||||
ng-show="inventory_name">
|
||||
<label class="col-lg-2 col-md-2 col-sm-2 col-xs-12
|
||||
StandardOutDetails-detailLabel">Inventory</label>
|
||||
<div class="col-lg-10 col-md-10 col-sm-10 col-xs-12
|
||||
StandardOutDetails-detailContent">
|
||||
<a href="{{ inventory_url }}"
|
||||
aw-tool-tip="The inventory this command ran on."
|
||||
data-placement="top">{{ inventory_name }}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group StandardOutDetails-detailRow
|
||||
StandardOutDetails-detailRow--closable"
|
||||
ng-show="credential_name">
|
||||
<label class="col-lg-2 col-md-2 col-sm-2 col-xs-12
|
||||
StandardOutDetails-detailLabel">Credential</label>
|
||||
<div class="col-lg-10 col-md-10 col-sm-10 col-xs-12
|
||||
StandardOutDetails-detailContent">
|
||||
<a href="{{ credential_url }}"
|
||||
aw-tool-tip="The credential used to run this command."
|
||||
data-placement="top">{{ credential_name }}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group StandardOutDetails-detailRow
|
||||
StandardOutDetails-detailRow--closable"
|
||||
ng-show="created_by">
|
||||
<label class="col-lg-2 col-md-2 col-sm-2 col-xs-12
|
||||
StandardOutDetails-detailContent">Launched By</label>
|
||||
<div class="col-lg-10 col-md-10 col-sm-10 col-xs-9
|
||||
StandardOutDetails-detailContent">
|
||||
<a href="/#/users/{{ created_by.id }}"
|
||||
aw-tool-tip="The user who ran this command."
|
||||
data-placement="top">{{ created_by.username }}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- since zero is a falsy value, you need ng-show such that
|
||||
the number is >= 0 -->
|
||||
<div class="form-group StandardOutDetails-detailRow
|
||||
StandardOutDetails-detailRow--closable"
|
||||
ng-show="forks >= 0">
|
||||
<label class="col-lg-2 col-md-2 col-sm-2 col-xs-12
|
||||
StandardOutDetails-detailLabel">Forks</label>
|
||||
<div class="col-lg-10 col-md-10 col-sm-10 col-xs-12
|
||||
StandardOutDetails-detailContent">{{ forks }}</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group StandardOutDetails-detailRow
|
||||
StandardOutDetails-detailRow--closable"
|
||||
ng-show="limit">
|
||||
<label class="col-lg-2 col-md-2 col-sm-2 col-xs-12
|
||||
StandardOutDetails-detailLabel">Limit</label>
|
||||
<div class="col-lg-10 col-md-10 col-sm-10 col-xs-12
|
||||
StandardOutDetails-detailContent">{{ limit }}</div>
|
||||
</div>
|
||||
|
||||
<!-- since zero is a falsy value, you need ng-show such that
|
||||
the number is >= 0 -->
|
||||
<div class="form-group StandardOutDetails-detailRow
|
||||
StandardOutDetails-detailRow--closable"
|
||||
ng-show="verbosity >= 0">
|
||||
<label class="col-lg-2 col-md-2 col-sm-2 col-xs-12
|
||||
StandardOutDetails-detailLabel">Verbosity</label>
|
||||
<div class="col-lg-10 col-md-10 col-sm-10 col-xs-12
|
||||
StandardOutDetails-detailContent">{{ verbosity }}</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group StandardOutDetails-closedToggle">
|
||||
<a class="col-sm-12 StandardOutDetails-closedToggleLink"
|
||||
ng-show="isClosed" href="javascript:;"
|
||||
ng-click="toggleClosedStatus()"> more <i class="fa fa-angle-down"></i>
|
||||
</a>
|
||||
<a class="col-sm-12 StandardOutDetails-closedToggleLink"
|
||||
ng-show="!isClosed" href="javascript:;"
|
||||
ng-click="toggleClosedStatus()"> less <i class="fa fa-angle-up"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default StandardOut-panel">
|
||||
<div class="panel-heading StandardOut-panelHeading">
|
||||
<h3 class="panel-title">Standard Output
|
||||
<a ng-href="/api/v1/ad_hoc_commands/{{ job.id }}/stdout?format=txt_download&token={{ token }}" class="btn btn-primary btn-xs DownloadStandardOut DownloadStandardOut--onStandardOutPage" id="download-stdout-button" type="button" aw-tool-tip="Download standard out as a .txt file" data-placement="top" ng-show="job.status === 'cancelled' || job.status === 'failed' || job.status === 'error' || job.status === 'successful'"><i class="fa fa-download DownloadStandardOut-icon DownloadStandardOut-icon--withText"></i>Download</a>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="panel-body stdout-panel-body StandardOut-panelBody">
|
||||
<div id="pre-container" class="body_background
|
||||
body_foreground pre mono-space StandardOut-preContainer"
|
||||
lr-infinite-scroll="stdOutScrollToTop"
|
||||
scroll-threshold="300" data-direction="up" time-threshold="500">
|
||||
<div id="pre-container-content" class="StandardOut-preContent"></div>
|
||||
</div>
|
||||
<div class="scroll-spinner" id="stdoutMoreRowsBottom">
|
||||
<i class="fa fa-cog fa-spin"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -172,9 +172,6 @@ angular.module('GeneratorHelpers', [systemStatus.name])
|
||||
case 'schedule':
|
||||
icon = "fa-calendar";
|
||||
break;
|
||||
case 'stdout':
|
||||
icon = "fa-external-link";
|
||||
break;
|
||||
case 'question_cancel':
|
||||
icon = 'fa-times';
|
||||
break;
|
||||
|
||||
@ -0,0 +1,116 @@
|
||||
<div class="tab-pane" id="jobs-stdout">
|
||||
<div ng-cloak id="htmlTemplate">
|
||||
<div class="StandardOut">
|
||||
<div class="StandardOut-leftPanel">
|
||||
<div class="Panel">
|
||||
<div class="StandardOut-panelHeader">
|
||||
RESULTS
|
||||
</div>
|
||||
<div class="StandardOut-details">
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="job.module_name">
|
||||
<div class="StandardOut-detailsLabel">Name</div>
|
||||
<div class="StandardOut-detailsContent">{{ job.module_name }}</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow">
|
||||
<div class="StandardOut-detailsLabel">STATUS</div>
|
||||
<div class="StandardOut-detailsContent">
|
||||
<i class="fa icon-job-{{ job.status }}"></i>
|
||||
<span class="StandardOut-statusText">{{ job.status }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="job.started">
|
||||
<div class="StandardOut-detailsLabel">STARTED</div>
|
||||
<div class="StandardOut-detailsContent">
|
||||
{{ job.started | date:'MM/dd/yy HH:mm:ss' }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="job.finished">
|
||||
<div class="StandardOut-detailsLabel">FINISHED</div>
|
||||
<div class="StandardOut-detailsContent">
|
||||
{{ job.finished | date:'MM/dd/yy HH:mm:ss' }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="job.finished">
|
||||
<div class="StandardOut-detailsLabel">ELAPSED</div>
|
||||
<div class="StandardOut-detailsContent">
|
||||
{{ job.elapsed }} seconds
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="job.module_args">
|
||||
<div class="StandardOut-detailsLabel">Module Args</div>
|
||||
<div class="StandardOut-detailsContent">{{ job.module_args }}</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow">
|
||||
<div class="StandardOut-detailsLabel">Inventory</div>
|
||||
<div class="StandardOut-detailsContent">
|
||||
<a href="{{ inventory_url }}"
|
||||
aw-tool-tip="The inventory this command ran on."
|
||||
data-placement="top">{{ inventory_name }}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="credential_name">
|
||||
<div class="StandardOut-detailsLabel">Credential</div>
|
||||
<div class="StandardOut-detailsContent">
|
||||
<a href="{{ credential_url }}"
|
||||
aw-tool-tip="The credential used to run this command."
|
||||
data-placement="top">{{ credential_name }}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="created_by">
|
||||
<div class="StandardOut-detailsLabel">Launched By</div>
|
||||
<div class="StandardOut-detailsContent">
|
||||
<a href="/#/users/{{ created_by.id }}"
|
||||
aw-tool-tip="The user who ran this command."
|
||||
data-placement="top">{{ created_by.username }}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- since zero is a falsy value, you need ng-show such that
|
||||
the number is >= 0 -->
|
||||
<div class="StandardOut-detailsRow" ng-show="forks >= 0">
|
||||
<div class="StandardOut-detailsLabel">Forks</div>
|
||||
<div class="StandardOut-detailsContent">{{ forks }}</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="limit">
|
||||
<div class="StandardOut-detailsLabel">Limit</div>
|
||||
<div class="StandardOut-detailsContent">{{ limit }}</div>
|
||||
</div>
|
||||
|
||||
<!-- since zero is a falsy value, you need ng-show such that
|
||||
the number is >= 0 -->
|
||||
<div class="StandardOut-detailsRow" ng-show="verbosity >= 0">
|
||||
<div class="StandardOut-detailsLabel">Verbosity</div>
|
||||
<div class="StandardOut-detailsContent">{{ verbosity }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="StandardOut-rightPanel">
|
||||
<div class="Panel">
|
||||
<div class="StandardOut-panelHeader">
|
||||
STANDARD OUT
|
||||
</div>
|
||||
<div class="StandardOut-consoleOutput">
|
||||
<div id="pre-container" class="body_background body_foreground pre mono-space StandardOut-preContainer"
|
||||
lr-infinite-scroll="stdOutScrollToTop" scroll-threshold="300" data-direction="up" time-threshold="500">
|
||||
<div id="pre-container-content" class="StandardOut-preContent"></div>
|
||||
</div>
|
||||
<div class="scroll-spinner" id="stdoutMoreRowsBottom">
|
||||
<i class="fa fa-cog fa-spin"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -0,0 +1,40 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2016 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
import {templateUrl} from '../../shared/template-url/template-url.factory';
|
||||
|
||||
export default {
|
||||
name: 'adHocJobStdout',
|
||||
route: '/ad_hoc_commands/:id/stdout',
|
||||
templateUrl: templateUrl('standard-out/adhoc/standard-out-adhoc'),
|
||||
controller: 'JobStdoutController',
|
||||
ncyBreadcrumb: {
|
||||
parent: "jobs",
|
||||
label: "{{ job.module_name }}"
|
||||
},
|
||||
data: {
|
||||
jobType: 'ad_hoc_commands'
|
||||
},
|
||||
resolve: {
|
||||
features: ['FeaturesService', function(FeaturesService) {
|
||||
return FeaturesService.get();
|
||||
}],
|
||||
adhocEventsSocket: ['Socket', '$rootScope', function(Socket, $rootScope) {
|
||||
// if (!$rootScope.adhoc_event_socket) {
|
||||
// $rootScope.adhoc_event_socket = Socket({
|
||||
// scope: $rootScope,
|
||||
// endpoint: "ad_hoc_command_events"
|
||||
// });
|
||||
// $rootScope.adhoc_event_socket.init();
|
||||
// return true;
|
||||
// } else {
|
||||
// return true;
|
||||
// }
|
||||
|
||||
return true;
|
||||
}]
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,130 @@
|
||||
<div class="tab-pane" id="jobs-stdout">
|
||||
<div ng-cloak id="htmlTemplate">
|
||||
<div class="StandardOut">
|
||||
<div class="StandardOut-leftPanel">
|
||||
<div class="Panel">
|
||||
<div class="StandardOut-panelHeader">
|
||||
RESULTS
|
||||
</div>
|
||||
<div class="StandardOut-details">
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="inventory_source_name">
|
||||
<div class="StandardOut-detailsLabel">NAME</div>
|
||||
<div class="StandardOut-detailsContent">
|
||||
<a href="/#/home/groups/?id={{ group }}">
|
||||
{{ inventory_source_name }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow">
|
||||
<div class="StandardOut-detailsLabel">STATUS</div>
|
||||
<div class="StandardOut-detailsContent">
|
||||
<i class="fa icon-job-{{ job.status }}"></i>
|
||||
<span class="StandardOut-statusText">{{ job.status }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="{{job.license_error !== null}}">
|
||||
<div class="StandardOut-detailsLabel">LICENSE ERROR</div>
|
||||
<div class="StandardOut-detailsContent">
|
||||
{{ job.license_error }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="job.started">
|
||||
<div class="StandardOut-detailsLabel">STARTED</div>
|
||||
<div class="StandardOut-detailsContent">
|
||||
{{ job.started | date:'MM/dd/yy HH:mm:ss' }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="job.finished">
|
||||
<div class="StandardOut-detailsLabel">FINISHED</div>
|
||||
<div class="StandardOut-detailsContent">
|
||||
{{ job.finished | date:'MM/dd/yy HH:mm:ss' }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="job.finished">
|
||||
<div class="StandardOut-detailsLabel">ELAPSED</div>
|
||||
<div class="StandardOut-detailsContent">
|
||||
{{ job.elapsed }} seconds
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="job.launch_type">
|
||||
<div class="StandardOut-detailsLabel">LAUNCH TYPE</div>
|
||||
<div class="StandardOut-detailsContent">
|
||||
{{ job.launch_type }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="credential_name">
|
||||
<div class="StandardOut-detailsLabel">CREDENTIAL</div>
|
||||
<div class="StandardOut-detailsContent">
|
||||
<a ui-sref="credentials.edit({credential_id: credential})">
|
||||
{{ credential_name }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="inventory_source_name">
|
||||
<div class="StandardOut-detailsLabel">GROUP</div>
|
||||
<div class="StandardOut-detailsContent">
|
||||
<a href="/#/home/groups/?id={{ group }}">
|
||||
{{ inventory_source_name }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="source">
|
||||
<div class="StandardOut-detailsLabel">SOURCE</div>
|
||||
<div class="StandardOut-detailsContent">
|
||||
{{ source }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="source_regions">
|
||||
<div class="StandardOut-detailsLabel">REGIONS</div>
|
||||
<div class="StandardOut-detailsContent">
|
||||
{{ source_regions }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="{{ job.overwrite !== null }}">
|
||||
<div class="StandardOut-detailsLabel">OVERWRITE</div>
|
||||
<div class="StandardOut-detailsContent">
|
||||
{{ job.overwrite }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="{{ job.overwrite_vars !== null }}">
|
||||
<div class="StandardOut-detailsLabel">OVERWRITE VARS</div>
|
||||
<div class="StandardOut-detailsContent">
|
||||
{{ job.overwrite_vars }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="StandardOut-rightPanel">
|
||||
<div class="Panel">
|
||||
<div class="StandardOut-panelHeader">
|
||||
STANDARD OUT
|
||||
</div>
|
||||
<div class="StandardOut-consoleOutput">
|
||||
<div id="pre-container" class="body_background body_foreground pre mono-space StandardOut-preContainer"
|
||||
lr-infinite-scroll="stdOutScrollToTop" scroll-threshold="300" data-direction="up" time-threshold="500">
|
||||
<div id="pre-container-content" class="StandardOut-preContent"></div>
|
||||
</div>
|
||||
<div class="scroll-spinner" id="stdoutMoreRowsBottom">
|
||||
<i class="fa fa-cog fa-spin"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -0,0 +1,42 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2016 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
import {templateUrl} from '../../shared/template-url/template-url.factory';
|
||||
|
||||
// TODO: figure out what this route should be - should it be inventory_sync?
|
||||
|
||||
export default {
|
||||
name: 'inventorySyncStdout',
|
||||
route: '/inventory_sync/:id/stdout',
|
||||
templateUrl: templateUrl('standard-out/inventory-sync/standard-out-inventory-sync'),
|
||||
controller: 'JobStdoutController',
|
||||
ncyBreadcrumb: {
|
||||
parent: "jobs",
|
||||
label: "{{ inventory_source_name }}"
|
||||
},
|
||||
data: {
|
||||
jobType: 'inventory_updates'
|
||||
},
|
||||
resolve: {
|
||||
features: ['FeaturesService', function(FeaturesService) {
|
||||
return FeaturesService.get();
|
||||
}],
|
||||
adhocEventsSocket: ['Socket', '$rootScope', function(Socket, $rootScope) {
|
||||
// if (!$rootScope.adhoc_event_socket) {
|
||||
// $rootScope.adhoc_event_socket = Socket({
|
||||
// scope: $rootScope,
|
||||
// endpoint: "ad_hoc_command_events"
|
||||
// });
|
||||
// $rootScope.adhoc_event_socket.init();
|
||||
// return true;
|
||||
// } else {
|
||||
// return true;
|
||||
// }
|
||||
|
||||
return true;
|
||||
}]
|
||||
}
|
||||
};
|
||||
20
awx/ui/client/src/standard-out/main.js
Normal file
20
awx/ui/client/src/standard-out/main.js
Normal file
@ -0,0 +1,20 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2016 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
import stdoutAdhocRoute from './adhoc/standard-out-adhoc.route';
|
||||
import stdoutManagementJobsRoute from './management-jobs/standard-out-management-jobs.route';
|
||||
import stdoutInventorySyncRoute from './inventory-sync/standard-out-inventory-sync.route';
|
||||
import stdoutScmUpdateRoute from './scm-update/standard-out-scm-update.route';
|
||||
import {JobStdoutController} from './standard-out.controller';
|
||||
|
||||
export default angular.module('standardOut', [])
|
||||
.controller('JobStdoutController', JobStdoutController)
|
||||
.run(['$stateExtender', function($stateExtender) {
|
||||
$stateExtender.addState(stdoutAdhocRoute);
|
||||
$stateExtender.addState(stdoutManagementJobsRoute);
|
||||
$stateExtender.addState(stdoutInventorySyncRoute);
|
||||
$stateExtender.addState(stdoutScmUpdateRoute);
|
||||
}]);
|
||||
@ -0,0 +1,82 @@
|
||||
<div class="tab-pane" id="jobs-stdout">
|
||||
<div ng-cloak id="htmlTemplate">
|
||||
<div class="StandardOut">
|
||||
<div class="StandardOut-leftPanel">
|
||||
<div class="Panel">
|
||||
<div class="StandardOut-panelHeader">
|
||||
RESULTS
|
||||
</div>
|
||||
<div class="StandardOut-details">
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="job.name">
|
||||
<div class="StandardOut-detailsLabel">NAME</div>
|
||||
<div class="StandardOut-detailsContent">{{ job.name }}</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow">
|
||||
<div class="StandardOut-detailsLabel">STATUS</div>
|
||||
<div class="StandardOut-detailsContent">
|
||||
<i class="fa icon-job-{{ job.status }}"></i>
|
||||
<span class="StandardOut-statusText">{{ job.status }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="job.started">
|
||||
<div class="StandardOut-detailsLabel">STARTED</div>
|
||||
<div class="StandardOut-detailsContent">
|
||||
{{ job.started | date:'MM/dd/yy HH:mm:ss' }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="job.finished">
|
||||
<div class="StandardOut-detailsLabel">FINISHED</div>
|
||||
<div class="StandardOut-detailsContent">
|
||||
{{ job.finished | date:'MM/dd/yy HH:mm:ss' }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="job.finished">
|
||||
<div class="StandardOut-detailsLabel">ELAPSED</div>
|
||||
<div class="StandardOut-detailsContent">
|
||||
{{ job.elapsed }} seconds
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="job.launch_type">
|
||||
<div class="StandardOut-detailsLabel">LAUNCH TYPE</div>
|
||||
<div class="StandardOut-detailsContent">
|
||||
{{ job.launch_type }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- TODO: figure out how to show the extra vars on different rows like the mockup -->
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="job.extra_vars">
|
||||
<div class="StandardOut-detailsLabel">EXTRA VARS</div>
|
||||
<div class="StandardOut-detailsContent">
|
||||
{{ job.extra_vars }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="StandardOut-rightPanel">
|
||||
<div class="Panel">
|
||||
<div class="StandardOut-panelHeader">
|
||||
STANDARD OUT
|
||||
</div>
|
||||
<div class="StandardOut-consoleOutput">
|
||||
<div id="pre-container" class="body_background body_foreground pre mono-space StandardOut-preContainer"
|
||||
lr-infinite-scroll="stdOutScrollToTop" scroll-threshold="300" data-direction="up" time-threshold="500">
|
||||
<div id="pre-container-content" class="StandardOut-preContent"></div>
|
||||
</div>
|
||||
<div class="scroll-spinner" id="stdoutMoreRowsBottom">
|
||||
<i class="fa fa-cog fa-spin"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -0,0 +1,40 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2016 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
import {templateUrl} from '../../shared/template-url/template-url.factory';
|
||||
|
||||
export default {
|
||||
name: 'managementJobStdout',
|
||||
route: '/management_jobs/:id/stdout',
|
||||
templateUrl: templateUrl('standard-out/management-jobs/standard-out-management-jobs'),
|
||||
controller: 'JobStdoutController',
|
||||
ncyBreadcrumb: {
|
||||
parent: "jobs",
|
||||
label: "{{ job.name }}"
|
||||
},
|
||||
data: {
|
||||
jobType: 'system_jobs'
|
||||
},
|
||||
resolve: {
|
||||
features: ['FeaturesService', function(FeaturesService) {
|
||||
return FeaturesService.get();
|
||||
}],
|
||||
adhocEventsSocket: ['Socket', '$rootScope', function(Socket, $rootScope) {
|
||||
// if (!$rootScope.adhoc_event_socket) {
|
||||
// $rootScope.adhoc_event_socket = Socket({
|
||||
// scope: $rootScope,
|
||||
// endpoint: "ad_hoc_command_events"
|
||||
// });
|
||||
// $rootScope.adhoc_event_socket.init();
|
||||
// return true;
|
||||
// } else {
|
||||
// return true;
|
||||
// }
|
||||
|
||||
return true;
|
||||
}]
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,95 @@
|
||||
<div class="tab-pane" id="jobs-stdout">
|
||||
<div ng-cloak id="htmlTemplate">
|
||||
<div class="StandardOut">
|
||||
<div class="StandardOut-leftPanel">
|
||||
<div class="Panel">
|
||||
<div class="StandardOut-panelHeader">
|
||||
RESULTS
|
||||
</div>
|
||||
<div class="StandardOut-details">
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="project_name">
|
||||
<div class="StandardOut-detailsLabel">NAME</div>
|
||||
<div class="StandardOut-detailsContent">
|
||||
<a ui-sref="projects.edit({id: job.project})">
|
||||
{{ project_name }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow">
|
||||
<div class="StandardOut-detailsLabel">STATUS</div>
|
||||
<div class="StandardOut-detailsContent">
|
||||
<i class="fa icon-job-{{ job.status }}"></i>
|
||||
<span class="StandardOut-statusText">{{ job.status }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="job.started">
|
||||
<div class="StandardOut-detailsLabel">STARTED</div>
|
||||
<div class="StandardOut-detailsContent">
|
||||
{{ job.started | date:'MM/dd/yy HH:mm:ss' }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="job.finished">
|
||||
<div class="StandardOut-detailsLabel">FINISHED</div>
|
||||
<div class="StandardOut-detailsContent">
|
||||
{{ job.finished | date:'MM/dd/yy HH:mm:ss' }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="job.finished">
|
||||
<div class="StandardOut-detailsLabel">ELAPSED</div>
|
||||
<div class="StandardOut-detailsContent">
|
||||
{{ job.elapsed }} seconds
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="job.launch_type">
|
||||
<div class="StandardOut-detailsLabel">LAUNCH TYPE</div>
|
||||
<div class="StandardOut-detailsContent">
|
||||
{{ job.launch_type }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="project_name">
|
||||
<div class="StandardOut-detailsLabel">PROJECT</div>
|
||||
<div class="StandardOut-detailsContent">
|
||||
<a ui-sref="projects.edit({id: job.project})">
|
||||
{{ project_name }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="StandardOut-detailsRow" ng-show="credential_name">
|
||||
<div class="StandardOut-detailsLabel">CREDENTIAL</div>
|
||||
<div class="StandardOut-detailsContent">
|
||||
<a ui-sref="credentials.edit({credential_id: credential})">
|
||||
{{ credential_name }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="StandardOut-rightPanel">
|
||||
<div class="Panel">
|
||||
<div class="StandardOut-panelHeader">
|
||||
STANDARD OUT
|
||||
</div>
|
||||
<div class="StandardOut-consoleOutput">
|
||||
<div id="pre-container" class="body_background body_foreground pre mono-space StandardOut-preContainer"
|
||||
lr-infinite-scroll="stdOutScrollToTop" scroll-threshold="300" data-direction="up" time-threshold="500">
|
||||
<div id="pre-container-content" class="StandardOut-preContent"></div>
|
||||
</div>
|
||||
<div class="scroll-spinner" id="stdoutMoreRowsBottom">
|
||||
<i class="fa fa-cog fa-spin"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -0,0 +1,42 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2016 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
import {templateUrl} from '../../shared/template-url/template-url.factory';
|
||||
|
||||
// TODO: figure out what this route should be - should it be scm_update?
|
||||
|
||||
export default {
|
||||
name: 'scmUpdateStdout',
|
||||
route: '/scm_update/:id/stdout',
|
||||
templateUrl: templateUrl('standard-out/scm-update/standard-out-scm-update'),
|
||||
controller: 'JobStdoutController',
|
||||
ncyBreadcrumb: {
|
||||
parent: "jobs",
|
||||
label: "{{ project_name }}"
|
||||
},
|
||||
data: {
|
||||
jobType: 'project_updates'
|
||||
},
|
||||
resolve: {
|
||||
features: ['FeaturesService', function(FeaturesService) {
|
||||
return FeaturesService.get();
|
||||
}],
|
||||
adhocEventsSocket: ['Socket', '$rootScope', function(Socket, $rootScope) {
|
||||
// if (!$rootScope.adhoc_event_socket) {
|
||||
// $rootScope.adhoc_event_socket = Socket({
|
||||
// scope: $rootScope,
|
||||
// endpoint: "ad_hoc_command_events"
|
||||
// });
|
||||
// $rootScope.adhoc_event_socket.init();
|
||||
// return true;
|
||||
// } else {
|
||||
// return true;
|
||||
// }
|
||||
|
||||
return true;
|
||||
}]
|
||||
}
|
||||
};
|
||||
60
awx/ui/client/src/standard-out/standard-out.block.less
Normal file
60
awx/ui/client/src/standard-out/standard-out.block.less
Normal file
@ -0,0 +1,60 @@
|
||||
@import "../shared/branding/colors.default.less";
|
||||
|
||||
/** @define StandardOut */
|
||||
|
||||
.StandardOut {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.StandardOut-leftPanel {
|
||||
flex: 0 0 400px;
|
||||
}
|
||||
|
||||
.StandardOut-rightPanel {
|
||||
flex: 1 0;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.StandardOut-panelHeader {
|
||||
color: @default-interface-txt;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
margin-right: 10px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.StandardOut-consoleOutput {
|
||||
margin-top: 25px;
|
||||
min-height: 200px;
|
||||
background-color: @default-secondary-bg;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.StandardOut-details {
|
||||
margin-top: 25px;
|
||||
}
|
||||
|
||||
.StandardOut-detailsRow {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.StandardOut-detailsRow:not(:last-child) {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.StandardOut-detailsLabel {
|
||||
width: 130px;
|
||||
flex: 0 0 130px;
|
||||
color: @default-interface-txt;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.StandardOut-detailsContent {
|
||||
flex: 1 0;
|
||||
}
|
||||
|
||||
.StandardOut-statusText {
|
||||
margin-left: 6px;
|
||||
}
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name controllers.function:JobStdout
|
||||
@ -11,11 +11,12 @@
|
||||
*/
|
||||
|
||||
|
||||
export function JobStdoutController ($location, $log, $rootScope, $scope, $compile, $stateParams, ClearScope, GetBasePath, Wait, Rest, ProcessErrors) {
|
||||
export function JobStdoutController ($location, $log, $rootScope, $scope, $compile, $state, $stateParams, ClearScope, GetBasePath, Wait, Rest, ProcessErrors, ModelToBasePathKey, Empty, GetChoices, LookUpName) {
|
||||
|
||||
ClearScope();
|
||||
|
||||
var job_id = $stateParams.id,
|
||||
jobType = $state.current.data.jobType,
|
||||
api_complete = false,
|
||||
stdout_url,
|
||||
current_range,
|
||||
@ -32,26 +33,27 @@ export function JobStdoutController ($location, $log, $rootScope, $scope, $compi
|
||||
$scope.isClosed = true;
|
||||
|
||||
|
||||
function openSockets() {
|
||||
if (/\/jobs\/(\d)+\/stdout/.test($location.$$url)) {
|
||||
$log.debug("socket watching on job_events-" + job_id);
|
||||
$rootScope.event_socket.on("job_events-" + job_id, function() {
|
||||
$log.debug("socket fired on job_events-" + job_id);
|
||||
if (api_complete) {
|
||||
event_queue++;
|
||||
}
|
||||
});
|
||||
} else if (/\/ad_hoc_commands\/(\d)+/.test($location.$$url)) {
|
||||
$log.debug("socket watching on ad_hoc_command_events-" + job_id);
|
||||
$rootScope.adhoc_event_socket.on("ad_hoc_command_events-" + job_id, function() {
|
||||
$log.debug("socket fired on ad_hoc_command_events-" + job_id);
|
||||
if (api_complete) {
|
||||
event_queue++;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
openSockets();
|
||||
// function openSockets() {
|
||||
// if (/\/jobs\/(\d)+\/stdout/.test($location.$$url)) {
|
||||
// $log.debug("socket watching on job_events-" + job_id);
|
||||
// $rootScope.event_socket.on("job_events-" + job_id, function() {
|
||||
// $log.debug("socket fired on job_events-" + job_id);
|
||||
// if (api_complete) {
|
||||
// event_queue++;
|
||||
// }
|
||||
// });
|
||||
// } else if (/\/ad_hoc_commands\/(\d)+/.test($location.$$url)) {
|
||||
// $log.debug("socket watching on ad_hoc_command_events-" + job_id);
|
||||
// $rootScope.adhoc_event_socket.on("ad_hoc_command_events-" + job_id, function() {
|
||||
// $log.debug("socket fired on ad_hoc_command_events-" + job_id);
|
||||
// if (api_complete) {
|
||||
// event_queue++;
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// openSockets();
|
||||
|
||||
if ($rootScope.removeJobStatusChange) {
|
||||
$rootScope.removeJobStatusChange();
|
||||
@ -158,9 +160,7 @@ export function JobStdoutController ($location, $log, $rootScope, $scope, $compi
|
||||
|
||||
$(".StandardOut").height($("body").height() - 60);
|
||||
|
||||
// Note: could be ad_hoc_commands or jobs
|
||||
var jobType = $location.path().replace(/^\//, '').split('/')[0];
|
||||
Rest.setUrl(GetBasePath(jobType) + job_id + '/');
|
||||
Rest.setUrl(GetBasePath('base') + jobType + '/' + job_id + '/');
|
||||
Rest.get()
|
||||
.success(function(data) {
|
||||
$scope.job = data;
|
||||
@ -182,13 +182,87 @@ export function JobStdoutController ($location, $log, $rootScope, $scope, $compi
|
||||
$scope.verbosity = data.verbosity;
|
||||
$scope.job_tags = data.job_tags;
|
||||
stdout_url = data.related.stdout;
|
||||
if (data.status === 'successful' || data.status === 'failed' || data.status === 'error' || data.status === 'canceled') {
|
||||
live_event_processing = false;
|
||||
if ($rootScope.jobStdOutInterval) {
|
||||
window.clearInterval($rootScope.jobStdOutInterval);
|
||||
|
||||
// If we have a source then we have to go get the source choices from the server
|
||||
if (!Empty(data.source)) {
|
||||
if ($scope.removeChoicesReady) {
|
||||
$scope.removeChoicesReady();
|
||||
}
|
||||
$scope.removeChoicesReady = $scope.$on('ChoicesReady', function() {
|
||||
$scope.source_choices.every(function(e) {
|
||||
if (e.value === data.source) {
|
||||
$scope.source = e.label;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
});
|
||||
// GetChoices can be found in the helper: LogViewer.js
|
||||
// It attaches the source choices to $scope.source_choices.
|
||||
// Then, when the callback is fired, $scope.source is bound
|
||||
// to the corresponding label.
|
||||
GetChoices({
|
||||
scope: $scope,
|
||||
url: GetBasePath('inventory_sources'),
|
||||
field: 'source',
|
||||
variable: 'source_choices',
|
||||
choice_name: 'choices',
|
||||
callback: 'ChoicesReady'
|
||||
});
|
||||
}
|
||||
|
||||
// LookUpName can be found in the helper: LogViewer.js
|
||||
// It attaches the name that it gets (based on the url)
|
||||
// to the $scope variable defined by the attribute scope_var.
|
||||
if (!Empty(data.credential)) {
|
||||
LookUpName({
|
||||
scope: $scope,
|
||||
scope_var: 'credential',
|
||||
url: GetBasePath('credentials') + data.credential + '/'
|
||||
});
|
||||
}
|
||||
|
||||
if (!Empty(data.inventory)) {
|
||||
LookUpName({
|
||||
scope: $scope,
|
||||
scope_var: 'inventory',
|
||||
url: GetBasePath('inventory') + data.inventory + '/'
|
||||
});
|
||||
}
|
||||
|
||||
if (!Empty(data.project)) {
|
||||
LookUpName({
|
||||
scope: $scope,
|
||||
scope_var: 'project',
|
||||
url: GetBasePath('projects') + data.project + '/'
|
||||
});
|
||||
}
|
||||
|
||||
if (!Empty(data.cloud_credential)) {
|
||||
LookUpName({
|
||||
scope: $scope,
|
||||
scope_var: 'cloud_credential',
|
||||
url: GetBasePath('credentials') + data.cloud_credential + '/'
|
||||
});
|
||||
}
|
||||
|
||||
if (!Empty(data.inventory_source)) {
|
||||
LookUpName({
|
||||
scope: $scope,
|
||||
scope_var: 'inventory_source',
|
||||
url: GetBasePath('inventory_sources') + data.inventory_source + '/'
|
||||
});
|
||||
}
|
||||
|
||||
// if (data.status === 'successful' || data.status === 'failed' || data.status === 'error' || data.status === 'canceled') {
|
||||
// live_event_processing = false;
|
||||
// if ($rootScope.jobStdOutInterval) {
|
||||
// window.clearInterval($rootScope.jobStdOutInterval);
|
||||
// }
|
||||
// }
|
||||
if(stdout_url) {
|
||||
$scope.$emit('LoadStdout');
|
||||
}
|
||||
$scope.$emit('LoadStdout');
|
||||
})
|
||||
.error(function(data, status) {
|
||||
ProcessErrors($scope, data, status, null, { hdr: 'Error!',
|
||||
@ -197,11 +271,9 @@ export function JobStdoutController ($location, $log, $rootScope, $scope, $compi
|
||||
|
||||
$scope.refresh = function(){
|
||||
if (loaded_sections.length === 0) { ////this if statement for refresh
|
||||
$log.debug('calling LoadStdout');
|
||||
$scope.$emit('LoadStdout');
|
||||
}
|
||||
else if (live_event_processing) {
|
||||
$log.debug('calling getNextSection');
|
||||
getNextSection();
|
||||
}
|
||||
};
|
||||
@ -281,4 +353,4 @@ export function JobStdoutController ($location, $log, $rootScope, $scope, $compi
|
||||
|
||||
}
|
||||
|
||||
JobStdoutController.$inject = [ '$location', '$log', '$rootScope', '$scope', '$compile', '$stateParams', 'ClearScope', 'GetBasePath', 'Wait', 'Rest', 'ProcessErrors'];
|
||||
JobStdoutController.$inject = [ '$location', '$log', '$rootScope', '$scope', '$compile', '$state', '$stateParams', 'ClearScope', 'GetBasePath', 'Wait', 'Rest', 'ProcessErrors', 'ModelToBasePathKey', 'Empty', 'GetChoices', 'LookUpName'];
|
||||
@ -1,47 +0,0 @@
|
||||
/** @define StandardOut */
|
||||
|
||||
.StandardOut {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.StandardOut-header {
|
||||
flex: initial;
|
||||
}
|
||||
|
||||
.StandardOut-breadcrumbs {
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
.StandardOut-form {
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
.StandardOut-panel {
|
||||
flex: 1 0 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: -41px;
|
||||
}
|
||||
|
||||
.StandardOut-panelHeading {
|
||||
flex: initial;
|
||||
}
|
||||
|
||||
.StandardOut-panelBody {
|
||||
flex: 1 0 auto;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.StandardOut-preContainer {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
}
|
||||
|
||||
.StandardOut-preContent {
|
||||
position: absolute;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user