|
|
@@ -8,20 +8,28 @@ type PropsType = {
|
|
|
|
|
|
type StateType = {
|
|
|
logs: string[],
|
|
|
- ws: any
|
|
|
+ ws: any,
|
|
|
+ scroll: boolean,
|
|
|
};
|
|
|
|
|
|
export default class Logs extends Component<PropsType, StateType> {
|
|
|
|
|
|
state = {
|
|
|
logs: [] as string[],
|
|
|
- ws : null as any
|
|
|
+ ws : null as any,
|
|
|
+ scroll: true,
|
|
|
}
|
|
|
|
|
|
+ ws = null as any;
|
|
|
scrollRef = React.createRef<HTMLDivElement>()
|
|
|
+ parentRef = React.createRef<HTMLDivElement>()
|
|
|
|
|
|
- scrollToBottom = () => {
|
|
|
- this.scrollRef.current.scrollTop = this.scrollRef.current.scrollHeight
|
|
|
+ scrollToBottom = (smooth: boolean) => {
|
|
|
+ if (smooth) {
|
|
|
+ this.parentRef.current.lastElementChild.scrollIntoView({ behavior: "smooth" })
|
|
|
+ } else {
|
|
|
+ this.parentRef.current.lastElementChild.scrollIntoView({ behavior: "auto" })
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
renderLogs = () => {
|
|
|
@@ -37,45 +45,73 @@ export default class Logs extends Component<PropsType, StateType> {
|
|
|
})
|
|
|
}
|
|
|
|
|
|
- componentDidMount() {
|
|
|
+ setupWebsocket = () => {
|
|
|
let { currentCluster, currentProject } = this.context;
|
|
|
let { selectedPod } = this.props;
|
|
|
if (!selectedPod.metadata?.name) return
|
|
|
+
|
|
|
let protocol = process.env.NODE_ENV == 'production' ? 'wss' : 'ws'
|
|
|
- let ws = new WebSocket(`${protocol}://${process.env.API_SERVER}/api/projects/${currentProject.id}/k8s/${selectedPod?.metadata?.namespace}/pod/${selectedPod?.metadata?.name}/logs?cluster_id=${currentCluster.id}&service_account_id=${currentCluster.service_account_id}`)
|
|
|
-
|
|
|
- this.setState({ ws }, () => {
|
|
|
- if (!this.state.ws) return;
|
|
|
-
|
|
|
- this.state.ws.onopen = () => {
|
|
|
- console.log('connected to websocket')
|
|
|
- }
|
|
|
-
|
|
|
- this.state.ws.onmessage = (evt: MessageEvent) => {
|
|
|
- this.setState({ logs: [...this.state.logs, evt.data] }, () => {
|
|
|
- this.scrollToBottom()
|
|
|
- })
|
|
|
- }
|
|
|
-
|
|
|
- this.state.ws.onerror = (err: ErrorEvent) => {
|
|
|
- console.log(err)
|
|
|
- }
|
|
|
- })
|
|
|
+ this.ws = new WebSocket(`${protocol}://${process.env.API_SERVER}/api/projects/${currentProject.id}/k8s/${selectedPod?.metadata?.namespace}/pod/${selectedPod?.metadata?.name}/logs?cluster_id=${currentCluster.id}&service_account_id=${currentCluster.service_account_id}`)
|
|
|
+
|
|
|
+ this.ws.onopen = () => {
|
|
|
+ console.log('connected to websocket')
|
|
|
+ }
|
|
|
+
|
|
|
+ this.ws.onmessage = (evt: MessageEvent) => {
|
|
|
+ this.setState({ logs: [...this.state.logs, evt.data] }, () => {
|
|
|
+ if (this.state.scroll && this.state.logs.length >50) {
|
|
|
+ this.scrollToBottom(false)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ this.ws.onerror = (err: ErrorEvent) => {
|
|
|
+ console.log("websocket error:", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ this.ws.onclose = () => {
|
|
|
+ console.log("closing pod logs")
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ refreshLogs = () => {
|
|
|
+ if (this.ws) {
|
|
|
+ this.ws.close();
|
|
|
+ this.ws = null;
|
|
|
+ this.setState({logs: []})
|
|
|
+ this.setupWebsocket();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ componentDidMount() {
|
|
|
+ this.setupWebsocket()
|
|
|
+ this.scrollToBottom(false);
|
|
|
}
|
|
|
|
|
|
componentWillUnmount() {
|
|
|
- if (this.state.ws) {
|
|
|
- console.log('closing websockets')
|
|
|
- this.state.ws.close()
|
|
|
+ console.log('log unmount')
|
|
|
+ if (this.ws) {
|
|
|
+ this.ws.close()
|
|
|
}
|
|
|
}
|
|
|
|
|
|
render() {
|
|
|
return (
|
|
|
- <LogStream ref={this.scrollRef}>
|
|
|
- <Wrapper>
|
|
|
+ <LogStream>
|
|
|
+ <Wrapper ref={this.parentRef}>
|
|
|
{this.renderLogs()}
|
|
|
+ <div ref={this.scrollRef} />
|
|
|
</Wrapper>
|
|
|
+ <Options>
|
|
|
+ <Scroll onClick={()=> {this.setState({scroll: !this.state.scroll}); this.scrollToBottom(true)}}>
|
|
|
+ <input type="checkbox" checked={this.state.scroll} onChange={() => {}}/>
|
|
|
+ Scroll to Bottom
|
|
|
+ </Scroll>
|
|
|
+ <Refresh onClick={() => {this.refreshLogs()}}>
|
|
|
+ <i className="material-icons">autorenew</i>
|
|
|
+ Refresh
|
|
|
+ </Refresh>
|
|
|
+ </Options>
|
|
|
</LogStream>
|
|
|
);
|
|
|
}
|
|
|
@@ -83,6 +119,54 @@ export default class Logs extends Component<PropsType, StateType> {
|
|
|
|
|
|
Logs.contextType = Context;
|
|
|
|
|
|
+const Scroll = styled.div`
|
|
|
+ align-items: center;
|
|
|
+ display: flex;
|
|
|
+ cursor: pointer;
|
|
|
+ width: 145px;
|
|
|
+ height: 100%;
|
|
|
+
|
|
|
+ :hover {
|
|
|
+ background: #2468d6;
|
|
|
+ }
|
|
|
+
|
|
|
+ > input {
|
|
|
+ width; 18px;
|
|
|
+ margin-left: 10px;
|
|
|
+ margin-right: 6px;
|
|
|
+ pointer-events: none;
|
|
|
+ }
|
|
|
+`
|
|
|
+
|
|
|
+const Refresh = styled.div`
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ width: 87px;
|
|
|
+ user-select: none;
|
|
|
+ cursor: pointer;
|
|
|
+ height: 100%;
|
|
|
+
|
|
|
+ > i {
|
|
|
+ margin-left: 6px;
|
|
|
+ font-size: 17px;
|
|
|
+ margin-right: 6px;
|
|
|
+ }
|
|
|
+
|
|
|
+ :hover {
|
|
|
+ background: #2468d6;
|
|
|
+ }
|
|
|
+`
|
|
|
+
|
|
|
+const Options = styled.div`
|
|
|
+ width: 100%;
|
|
|
+ height: 25px;
|
|
|
+ background: #397ae3;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+`
|
|
|
+
|
|
|
const Wrapper = styled.div`
|
|
|
width: 100%;
|
|
|
height: 100%;
|
|
|
@@ -92,6 +176,7 @@ const Wrapper = styled.div`
|
|
|
|
|
|
const LogStream = styled.div`
|
|
|
display: flex;
|
|
|
+ flex-direction: column;
|
|
|
flex: 1;
|
|
|
float: right;
|
|
|
height: 100%;
|