diff --git a/client/src/services/api.ts b/client/src/services/api.ts index 6faa62c..325f046 100644 --- a/client/src/services/api.ts +++ b/client/src/services/api.ts @@ -1,6 +1,6 @@ import _ from 'lodash'; import {Base64} from 'js-base64'; -import {request, post, stream, apiFactory, apiFactoryWithNamespace} from './apiProxy'; +import { request, post, stream, apiFactory, apiFactoryWithNamespace, requestText } from './apiProxy'; import log from '../utils/log'; import {K8sEvent, Namespace, TODO, Metrics, PersistentVolume, Node, Pod, ClusterRole, ClusterRoleBinding, ConfigMap, RoleBinding, Secret, ServiceAccount, StorageClass} from '../utils/types'; @@ -41,6 +41,7 @@ const apis = { testAuth, getRules, logs, + logsAll, swagger, exec, metrics: metricsFactory(), @@ -162,6 +163,11 @@ function exec(namespace: string, name: string, container: string, cb: DataCallba return stream(url, cb, {additionalProtocols, isJson: false}); } +function logsAll(namespace: string, name: string, container: string, showPrevious: boolean) { + const url = `/api/v1/namespaces/${namespace}/pods/${name}/log?container=${container}&previous=${showPrevious}`; + return requestText(url); +} + function logs(namespace: string, name: string, container: string, tailLines: number, showPrevious: boolean, cb: DataCallback) { const items: string[] = []; const url = `/api/v1/namespaces/${namespace}/pods/${name}/log?container=${container}&previous=${showPrevious}&tailLines=${tailLines}&follow=true`; diff --git a/client/src/services/apiProxy.ts b/client/src/services/apiProxy.ts index a627f2e..00a94b0 100644 --- a/client/src/services/apiProxy.ts +++ b/client/src/services/apiProxy.ts @@ -25,7 +25,7 @@ const BASE_HTTP_URL = isDev && hostname === 'localhost' ? 'http://localhost:4654 const BASE_WS_URL = BASE_HTTP_URL.replace('http', 'ws'); const JSON_HEADERS = {Accept: 'application/json', 'Content-Type': 'application/json'}; -export async function request(path: string, params?: any, autoLogoutOnAuthError = true) { +async function requestInner(path: string, params?: any, autoLogoutOnAuthError = true) { const opts = Object.assign({headers: {}}, params); const token = getToken(); @@ -55,7 +55,15 @@ export async function request(path: string, params?: any, autoLogoutOnAuthError throw error; } - return response.json(); + return response; +} + +export async function request(path: string, params?: any, autoLogoutOnAuthError = true) { + return (await requestInner(path, params, autoLogoutOnAuthError)).json(); +} + +export async function requestText(path: string, params?: any, autoLogoutOnAuthError = true) { + return (await requestInner(path, params, autoLogoutOnAuthError)).text(); } export function apiFactory>(group: string, version: string, resource: string) { diff --git a/client/src/views/logs.tsx b/client/src/views/logs.tsx index 1b85be4..713dcb9 100644 --- a/client/src/views/logs.tsx +++ b/client/src/views/logs.tsx @@ -9,6 +9,9 @@ import InputFilter from '../components/inputFilter'; import Loading from '../components/loading'; import api from '../services/api'; import {Pod} from '../utils/types'; +import Button from '../components/button'; +import LogsSvg from '../art/logsSvg'; +import LoadingSvg from '../art/loadingSvg'; type Props = { namespace: string; @@ -23,6 +26,7 @@ type State = { container?: string; initContainers?: string[]; filter?: string; + logDownloading: boolean; } export default class Logs extends Base { @@ -31,6 +35,7 @@ export default class Logs extends Base { state: State = { showPrevious: false, items: [], + logDownloading: false, }; constructor(props: Props) { @@ -41,6 +46,7 @@ export default class Logs extends Base { // than once during the wait timeout." const options = {leading: true, trailing: true, maxWait: 1000}; this.debouncedSetState = _.debounce(this.setState.bind(this), 100, options); + this.startDownloadLog = this.startDownloadLog.bind(this); } componentDidMount() { @@ -85,9 +91,38 @@ export default class Logs extends Base { }); } + startDownloadLog() { + const {namespace, name} = this.props; + const {container, showPrevious = false, logDownloading} = this.state; + + if (!container || logDownloading) { + return; + } + + this.setState({logDownloading: true}, async () => { + const message = await api.logsAll(namespace, name, container, showPrevious).catch(() => { + this.setState({logDownloading: false}); + }); + if (message) { + const blob = new Blob([message], { + type: 'text/plain;charset=utf8', + }); + const url = window.URL.createObjectURL(blob); + + const element = document.createElement('a'); + element.setAttribute('href', url); + element.setAttribute('download', `${name}.log`); + element.click(); + window.URL.revokeObjectURL(url); + this.setState({logDownloading: false}); + } + }); + + } + render() { const {namespace, name} = this.props; - const {items, container, containers = [], initContainers = [], filter = '', showPrevious = false} = this.state; + const {items, container, containers = [], initContainers = [], filter = '', showPrevious = false, logDownloading} = this.state; const lowercaseFilter = filter.toLowerCase(); const filteredLogs = items.filter(x => x.toLowerCase().includes(lowercaseFilter)); @@ -133,10 +168,19 @@ export default class Logs extends Base {
Previous
+ this.setState({filter: x})} /> + +