提交 4a27b78e 编写于 作者: P Piotr Bryk

Merge pull request #255 from bryk/fix-logs

Implement logs link in the details page
......@@ -87,6 +87,10 @@ func CreateHttpApiHandler(client *client.Client) http.Handler {
logsWs := new(restful.WebService)
logsWs.Path("/api/logs").
Produces(restful.MIME_JSON)
logsWs.Route(
logsWs.GET("/{namespace}/{podId}").
To(apiHandler.handleLogs).
Writes(Logs{}))
logsWs.Route(
logsWs.GET("/{namespace}/{podId}/{container}").
To(apiHandler.handleLogs).
......@@ -259,7 +263,11 @@ func (apiHandler *ApiHandler) handleLogs(request *restful.Request, response *res
namespace := request.PathParameter("namespace")
podId := request.PathParameter("podId")
container := request.PathParameter("container")
result, err := GetPodLogs(apiHandler.client, namespace, podId, container)
var containerPtr *string = nil
if len(container) > 0 {
containerPtr = &container
}
result, err := GetPodLogs(apiHandler.client, namespace, podId, containerPtr)
if err != nil {
handleInternalError(response, err)
return
......
......@@ -26,17 +26,23 @@ import (
// Log response structure
type Logs struct {
// Pod id
// Pod name.
PodId string `json:"podId"`
// Specific time when logs started
// Specific time when logs started.
SinceTime unversioned.Time `json:"sinceTime"`
// Logs string
// Logs string lines.
Logs []string `json:"logs"`
// The name of the container the logs are for.
Container string `json:"container"`
}
// Return logs for particular pod and container or error when occurred.
func GetPodLogs(client *client.Client, namespace, podId, container string) (*Logs, error) {
log.Printf("Getting logs from %s container from %s pod in %s namespace", container, podId,
// Return logs for particular pod and container or error when occurred. When container is null,
// logs for the first one are returned.
func GetPodLogs(client *client.Client, namespace, podId string, container *string) (*Logs, error) {
log.Printf("Getting logs from %v container from %s pod in %s namespace", container, podId,
namespace)
pod, err := client.Pods(namespace).Get(podId)
......@@ -44,8 +50,12 @@ func GetPodLogs(client *client.Client, namespace, podId, container string) (*Log
return nil, err
}
if container == nil {
container = &pod.Spec.Containers[0].Name
}
logOptions := &api.PodLogOptions{
Container: container,
Container: *container,
Follow: false,
Previous: false,
Timestamps: true,
......
......@@ -81,7 +81,9 @@ func GetReplicaSetPods(client *client.Client, namespace, name string, limit int)
// Data is sorted by total number of restarts for replica set pod.
// Result set can be limited
func getReplicaSetPods(pods []api.Pod, limit int) *ReplicaSetPods {
replicaSetPods := &ReplicaSetPods{}
replicaSetPods := &ReplicaSetPods{
Pods: make([]ReplicaSetPodWithContainers, 0),
}
for _, pod := range pods {
totalRestartCount := 0
replicaSetPodWithContainers := ReplicaSetPodWithContainers{
......
......@@ -199,7 +199,8 @@ backendApi.ReplicaSetPods;
* @typedef {{
* podId: string,
* sinceTime: string,
* logs: !Array<string>
* logs: !Array<string>,
* container: string
* }}
*/
backendApi.Logs;
......
......@@ -30,7 +30,12 @@ body {
}
a {
color: $primary;
text-decoration: inherit;
&:visited {
color: $primary;
}
}
.kd-toolbar-tools,
......@@ -53,3 +58,10 @@ a {
right: 0;
top: 0;
}
.kd-text-icon {
font-size: inherit;
height: inherit;
vertical-align: middle;
}
......@@ -29,16 +29,16 @@ export class LogsController {
/** @private {!./logs_service.LogColorInversionService} */
this.logsColorInversionService_ = logsColorInversionService;
/**
* Indicates color state of log area.
* If false: black text is placed on white area. Otherwise colors are inverted.
* @export
* @return {boolean}
*/
this.isTextColorInverted = function() { return this.logsColorInversionService_.getInverted(); };
}
/**
* Indicates state of log area color.
* If false: black text is placed on white area. Otherwise colors are inverted.
* @export
* @return {boolean}
*/
isTextColorInverted() { return this.logsColorInversionService_.getInverted(); }
/**
* Return proper style class for logs content.
* @export
......
......@@ -26,7 +26,7 @@ export class StateParams {
* @param {string} namespace
* @param {string} replicaSet
* @param {string} podId
* @param {string} container
* @param {string=} [container]
*/
constructor(namespace, replicaSet, podId, container) {
/** @export {string} Namespace of this Replica Set. */
......@@ -38,7 +38,7 @@ export class StateParams {
/** @export {string} Id of this Pod. */
this.podId = podId;
/** @export {string} Name of this pod container. */
/** @export {string|undefined} Name of this pod container. */
this.container = container;
}
}
......@@ -38,7 +38,7 @@ export default function stateConfig($stateProvider) {
};
$stateProvider.state(stateName, {
url: '/logs/:namespace/:replicaSet/:podId/:container',
url: '/logs/:namespace/:replicaSet/:podId/:container?',
resolve: {
'replicaSetPods': resolveReplicaSetPods,
'podLogs': resolvePodLogs,
......@@ -55,7 +55,8 @@ export default function stateConfig($stateProvider) {
*/
function resolveReplicaSetPods($stateParams, $resource) {
/** @type {!angular.Resource<!backendApi.ReplicaSetPods>} */
let resource = $resource('/api/replicasets/pods/:namespace/:replicaSet', $stateParams);
let resource =
$resource(`/api/replicasets/pods/${$stateParams.namespace}/${$stateParams.replicaSet}`);
return resource.get().$promise;
}
......@@ -68,7 +69,7 @@ function resolveReplicaSetPods($stateParams, $resource) {
*/
function resolvePodLogs($stateParams, $resource) {
/** @type {!angular.Resource<!backendApi.Logs>} */
let resource = $resource('/api/logs/:namespace/:podId/:container', $stateParams);
let resource = $resource(`/api/logs/${$stateParams.namespace}/${$stateParams.podId}/${$stateParams.container}`);
return resource.get().$promise;
}
......@@ -23,36 +23,34 @@ export default class LogsToolbarController {
* @param {!ui.router.$state} $state
* @param {!StateParams} $stateParams
* @param {!backendApi.ReplicaSetPods} replicaSetPods
* @param {!backendApi.Logs} podLogs
* @param {!../logs_service.LogColorInversionService} logsColorInversionService
* @ngInject
*/
constructor($state, $stateParams, replicaSetPods, logsColorInversionService) {
constructor($state, $stateParams, replicaSetPods, podLogs, logsColorInversionService) {
/** @private {!ui.router.$state} */
this.state_ = $state;
/** @private {!StateParams} */
this.params = $stateParams;
/**
* Service to notify logs controller if any changes on toolbar.
* @private {!../logs_service.LogColorInversionService}
*/
this.logsColorInversionService = logsColorInversionService;
this.logsColorInversionService_ = logsColorInversionService;
/** @export {!Array<!backendApi.ReplicaSetPodWithContainers>} */
this.pods = replicaSetPods.pods || [];
this.pods = replicaSetPods.pods;
/**
* Currently chosen pod.
* @export {!backendApi.ReplicaSetPodWithContainers|undefined}
*/
this.pod = this.findPodByName_(this.pods, this.params.podId);
this.pod = this.findPodByName_(this.pods, $stateParams.podId);
/** @export {!Array<!backendApi.PodContainer>} */
this.containers = this.pod.podContainers || [];
this.containers = this.pod.podContainers;
/** @export {!backendApi.PodContainer|undefined} */
this.container = this.findContainerByName_(this.containers, this.params.container);
/** @export {!backendApi.PodContainer} */
this.container = this.initializeContainer_(this.containers, podLogs.container);
/**
* Pod creation time.
......@@ -64,23 +62,23 @@ export default class LogsToolbarController {
* Namespace.
* @private {string}
*/
this.namespace = this.params.namespace;
this.namespace_ = $stateParams.namespace;
/**
* Replica Set name.
* @private {string}
*/
this.replicaSetName = this.params.replicaSet;
/**
* Indicates state of log area color.
* If false: black text is placed on white area. Otherwise colors are inverted.
* @export
* @return {boolean}
*/
this.isTextColorInverted = function() { return this.logsColorInversionService.getInverted(); };
this.replicaSetName_ = $stateParams.replicaSet;
}
/**
* Indicates state of log area color.
* If false: black text is placed on white area. Otherwise colors are inverted.
* @export
* @return {boolean}
*/
isTextColorInverted() { return this.logsColorInversionService_.getInverted(); }
/**
* Execute a code when a user changes the selected option of a pod element.
* @param {string} podId
......@@ -89,7 +87,7 @@ export default class LogsToolbarController {
*/
onPodChange(podId) {
return this.state_.transitionTo(
logs, new StateParams(this.namespace, this.replicaSetName, podId, this.container.name));
logs, new StateParams(this.namespace_, this.replicaSetName_, podId, this.container.name));
}
/**
......@@ -100,7 +98,7 @@ export default class LogsToolbarController {
*/
onContainerChange(container) {
return this.state_.transitionTo(
logs, new StateParams(this.namespace, this.replicaSetName, this.pod.name, container));
logs, new StateParams(this.namespace_, this.replicaSetName_, this.pod.name, container));
}
/**
......@@ -120,7 +118,7 @@ export default class LogsToolbarController {
* Execute a code when a user changes the selected option for console color.
* @export
*/
onTextColorChange() { this.logsColorInversionService.invert(); }
onTextColorChange() { this.logsColorInversionService_.invert(); }
/**
* Find Pod by name.
......@@ -136,9 +134,15 @@ export default class LogsToolbarController {
* Find Container by name.
* Return object or undefined if can not find a object.
* @param {!Array<!backendApi.PodContainer>} array
* @param {!string} name
* @return {!backendApi.PodContainer|undefined}
* @param {string} name
* @return {!backendApi.PodContainer}
* @private
*/
findContainerByName_(array, name) { return array.find((element) => element.name === name); }
initializeContainer_(array, name) {
let container = array.find((element) => element.name === name);
if (!container) {
container = array[0];
}
return container;
}
}
......@@ -157,7 +157,7 @@ limitations under the License.
</th>
<th class="kd-replicasetdetail-table-header">
<span>Logs
<i class="material-icons kd-replicasetdetail-help-icon">
<i class="material-icons kd-text-icon">
help
<md-tooltip>Display logs from the pod</md-tooltip>
</i>
......@@ -208,9 +208,11 @@ limitations under the License.
</td>
<td class="kd-replicasetdetail-table-cell">
<span>
Logs<i class="material-icons kd-replicasetdetail-table-icon">arrow_drop_down</i>
<a ng-href="{{::ctrl.getPodLogsHref(pod)}}" class="md-primary">
Logs
<i class="material-icons kd-text-icon">open_in_new</i>
</a>
</span>
<!-- TODO(maciaszczykm): Handle it. -->
</td>
<td class="kd-replicasetdetail-table-cell">
<md-button class="md-icon-button">
......
......@@ -119,7 +119,4 @@ $table-cell-height-half: $table-cell-height / 2;
.kd-replicasetdetail-help-icon {
color: $foreground-2;
cursor: default;
font-size: inherit;
height: inherit;
vertical-align: text-top;
}
......@@ -15,6 +15,8 @@
import showUpdateReplicasDialog from 'replicasetdetail/updatereplicas_dialog';
import {UPWARDS, DOWNWARDS} from 'replicasetdetail/sortedheader_controller';
import {stateName as replicasets} from 'replicasetlist/replicasetlist_state';
import {stateName as logsStateName} from 'logs/logs_state';
import {StateParams as LogsStateParams} from 'logs/logs_state';
// Filter type and source values for events.
const EVENT_ALL = 'All';
......@@ -131,6 +133,15 @@ export default class ReplicaSetDetailController {
this.events = this.filterBySource(this.events, this.eventSource);
}
/**
* @export
*/
getPodLogsHref(pod) {
return this.state_.href(
logsStateName,
new LogsStateParams(this.stateParams_.namespace, this.stateParams_.replicaSet, pod.name));
}
/**
* Filters events by their type.
* @param {!Array<!backendApi.Event>} events
......
......@@ -14,6 +14,7 @@
import componentsModule from './../common/components/components_module';
import filtersModule from 'common/filters/filters_module';
import logsModule from 'logs/logs_module';
import serviceEndpointDirective from './serviceendpoint_directive';
import stateConfig from './replicasetdetail_stateconfig';
import sortedHeaderDirective from './sortedheader_directive';
......@@ -30,8 +31,9 @@ export default angular.module(
'ngMaterial',
'ngResource',
'ui.router',
filtersModule.name,
componentsModule.name,
filtersModule.name,
logsModule.name,
])
.config(stateConfig)
.directive('kdServiceEndpoint', serviceEndpointDirective)
......
......@@ -16,17 +16,17 @@ limitations under the License.
<button class="kd-replicaset-detail-table-header-button" ng-click="sortCtrl.changeSorting()">
<md-icon md-font-library="material-icons" ng-show="sortCtrl.isArrowDown()"
class="kd-sortedheader-sort-icon">
class="kd-text-icon">
arrow_downward
</md-icon>
<md-icon md-font-library="material-icons" ng-show="sortCtrl.isArrowUp()"
class="kd-sortedheader-sort-icon">
class="kd-text-icon">
arrow_upward
</md-icon>
<ng-transclude></ng-transclude>
</button>
<md-icon ng-show="sortCtrl.isTooltipAvailable()" md-font-library="material-icons"
class="kd-replicasetdetail-help-icon">
class="kd-replicasetdetail-help-icon kd-text-icon">
help
<md-tooltip>{{sortCtrl.tooltip}}</md-tooltip>
</md-icon>
......@@ -24,10 +24,3 @@
outline: none;
}
}
.kd-sortedheader-sort-icon {
font-size: inherit;
height: inherit;
vertical-align: text-top;
width: inherit;
}
......@@ -30,7 +30,7 @@ limitations under the License.
{{::(pod.name | middleEllipsis:10)}}
</div>
<div class="kd-menu-logs-item kd-menu-logs-item-since">
<a ng-href="{{::ctrl.getLogsHref(pod.name, pod.podContainers[0].name)}}"
<a ng-href="{{::ctrl.getLogsHref(pod.name)}}"
ng-if="::ctrl.podContainerExists(pod)">
<span ng-if="::pod.startTime">
{{pod.startTime | date:"short"}}
......@@ -38,13 +38,13 @@ limitations under the License.
<span ng-if="::!pod.startTime">
Not running
</span>
<i class="material-icons kd-menu-logs-link-icon">open_in_new</i>
<i class="material-icons kd-text-icon">open_in_new</i>
</a>
<span ng-if="::!ctrl.podContainerExists(pod)">-</span>
</div>
<div class="kd-menu-logs-item kd-menu-logs-item-prior">
<a ng-if="::ctrl.podContainersRestarted(pod)">
Logs<i class="material-icons kd-menu-logs-link-icon">open_in_new</i>
Logs <i class="material-icons kd-text-icon">open_in_new</i>
</a>
<span ng-if="::!ctrl.podContainersRestarted(pod)">-</span>
</div>
......
......@@ -90,14 +90,12 @@ export default class LogsMenuController {
/**
* @param {string} podName
* @param {string} containerName
* @return {string}
* @export
*/
getLogsHref(podName, containerName) {
getLogsHref(podName) {
return this.state_.href(
logsStateName,
new StateParams(this.namespace, this.replicaSetName, podName, containerName));
logsStateName, new StateParams(this.namespace, this.replicaSetName, podName));
}
/**
......
......@@ -24,6 +24,10 @@
display: inline-block;
font-weight: $regular-font-weight;
text-decoration: none;
&:visited {
color: inherit;
}
}
.kd-replicaset-card-title {
......@@ -106,8 +110,3 @@
.kd-menu-logs-item-since {
width: 15 * $baseline-grid;
}
.kd-menu-logs-link-icon {
font-size: $subhead-font-size-base;
margin-left: $baseline-grid;
}
......@@ -72,7 +72,7 @@ func TestGetReplicaSetPods(t *testing.T) {
limit int
expected *ReplicaSetPods
}{
{nil, 0, &ReplicaSetPods{}},
{nil, 0, &ReplicaSetPods{Pods: []ReplicaSetPodWithContainers{}}},
{pods, 10, &ReplicaSetPods{Pods: []ReplicaSetPodWithContainers{
{
Name: "pod-2",
......
......@@ -48,6 +48,11 @@ describe('Logs toolbar controller', () => {
],
};
/** @type {!backendApi.Logs} podLogs */
const logs = {
container: mockContainer,
};
/** @type {!StateParams} */
const stateParams = new StateParams(mockNamespace, mockReplicaSet, mockPodId, mockContainer);
......@@ -66,8 +71,8 @@ describe('Logs toolbar controller', () => {
angular.mock.inject(($controller, $state) => {
state = $state;
ctrl = $controller(
LogsToolbarController, {replicaSetPods: replicaSetPods, $stateParams: stateParams},
$state);
LogsToolbarController,
{replicaSetPods: replicaSetPods, $stateParams: stateParams, podLogs: logs}, $state);
});
});
......
......@@ -28,9 +28,15 @@ describe('Replica Set Detail controller', () => {
beforeEach(() => {
angular.mock.module(replicaSetDetailModule.name);
angular.mock.inject(($resource, $log, $state, $mdDialog) => {
angular.mock.inject(($controller, $mdDialog, $resource) => {
mdDialog = $mdDialog;
ctrl = new ReplicaSetDetailController(mdDialog, $state, $resource, $log, [], [], []);
ctrl = $controller(ReplicaSetDetailController, {
replicaSetDetail: {},
replicaSetEvents: {},
replicaSetDetailResource: $resource('/foo'),
replicaSetSpecPodsResource: $resource('/bar'),
$stateParams: {replicaSet: 'foo-replicaset', namespace: 'foo-namespace'},
});
});
});
......@@ -93,4 +99,9 @@ describe('Replica Set Detail controller', () => {
// then
expect(mdDialog.show).toHaveBeenCalled();
});
it('should create logs href', () => {
expect(ctrl.getPodLogsHref({name: 'foo-pod'}))
.toBe('#/logs/foo-namespace/foo-replicaset/foo-pod/');
});
});
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册