Added node and type selector UI to Error Logs screen (#1715)
This commit is contained in:
@@ -55,6 +55,29 @@ func RestartService() (*http.Response, error) {
|
||||
return response, err
|
||||
}
|
||||
|
||||
func GetNodes() (*http.Response, error) {
|
||||
/*
|
||||
Helper function to get nodes
|
||||
HTTP Verb: GET
|
||||
URL: /api/v1/nodes
|
||||
*/
|
||||
request, err := http.NewRequest(
|
||||
"GET",
|
||||
"http://localhost:9090/api/v1/nodes",
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
request.Header.Add("Cookie", fmt.Sprintf("token=%s", token))
|
||||
request.Header.Add("Content-Type", "application/json")
|
||||
client := &http.Client{
|
||||
Timeout: 2000 * time.Second, // increased timeout since restart takes time, more than other APIs.
|
||||
}
|
||||
response, err := client.Do(request)
|
||||
return response, err
|
||||
}
|
||||
|
||||
func NotifyPostgres() (*http.Response, error) {
|
||||
/*
|
||||
Helper function to add Postgres Notification
|
||||
@@ -257,3 +280,23 @@ func TestListUsersWithAccessToBucket(t *testing.T) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestGetNodes(t *testing.T) {
|
||||
|
||||
assert := assert.New(t)
|
||||
getNodesResponse, getNodesError := GetNodes()
|
||||
assert.Nil(getNodesError)
|
||||
if getNodesError != nil {
|
||||
log.Println(getNodesError)
|
||||
return
|
||||
}
|
||||
addObjRsp := inspectHTTPResponse(getNodesResponse)
|
||||
if getNodesResponse != nil {
|
||||
assert.Equal(
|
||||
200,
|
||||
getNodesResponse.StatusCode,
|
||||
addObjRsp,
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,10 +19,24 @@ import { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
import { connect } from "react-redux";
|
||||
import { Box, Grid } from "@mui/material";
|
||||
import {
|
||||
Grid,
|
||||
FormControl,
|
||||
MenuItem,
|
||||
Select,
|
||||
InputBase,
|
||||
Button,
|
||||
} from "@mui/material";
|
||||
|
||||
import moment from "moment/moment";
|
||||
import { ErrorResponseHandler } from "../../../../../src/common/types";
|
||||
import api from "../../../../../src/common/api";
|
||||
import { AppState } from "../../../../store";
|
||||
import { logMessageReceived, logResetMessages } from "../actions";
|
||||
import {
|
||||
logMessageReceived,
|
||||
logResetMessages,
|
||||
setLogsStarted,
|
||||
} from "../actions";
|
||||
import { LogMessage } from "../types";
|
||||
import { wsProtocol } from "../../../../utils/wsUtils";
|
||||
import {
|
||||
@@ -30,6 +44,7 @@ import {
|
||||
containerForHeader,
|
||||
logsCommon,
|
||||
searchField,
|
||||
inlineCheckboxes,
|
||||
} from "../../Common/FormComponents/common/styleLibrary";
|
||||
import PageHeader from "../../Common/PageHeader/PageHeader";
|
||||
import PageLayout from "../../Common/Layout/PageLayout";
|
||||
@@ -60,36 +75,83 @@ const styles = (theme: Theme) =>
|
||||
color: "#A52A2A",
|
||||
paddingLeft: 25,
|
||||
},
|
||||
nodeField: {
|
||||
width: "100%",
|
||||
},
|
||||
ansidefault: {
|
||||
color: "#000",
|
||||
},
|
||||
midColumnCheckboxes: {
|
||||
display: "flex",
|
||||
},
|
||||
checkBoxLabel: {
|
||||
marginTop: 10,
|
||||
fontSize: 16,
|
||||
fontWeight: 500,
|
||||
},
|
||||
highlight: {
|
||||
"& span": {
|
||||
backgroundColor: "#082F5238",
|
||||
},
|
||||
},
|
||||
...actionsTray,
|
||||
actionsTray: {
|
||||
...actionsTray.actionsTray,
|
||||
marginBottom: 0,
|
||||
},
|
||||
...searchField,
|
||||
...logsCommon,
|
||||
...inlineCheckboxes,
|
||||
...containerForHeader(theme.spacing(4)),
|
||||
});
|
||||
|
||||
const SelectStyled = withStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
root: {
|
||||
lineHeight: "50px",
|
||||
"label + &": {
|
||||
marginTop: theme.spacing(3),
|
||||
},
|
||||
"& .MuiSelect-select:focus": {
|
||||
backgroundColor: "transparent",
|
||||
},
|
||||
},
|
||||
input: {
|
||||
height: 50,
|
||||
fontSize: 13,
|
||||
lineHeight: "50px",
|
||||
},
|
||||
})
|
||||
)(InputBase);
|
||||
|
||||
interface ILogs {
|
||||
classes: any;
|
||||
logMessageReceived: typeof logMessageReceived;
|
||||
logResetMessages: typeof logResetMessages;
|
||||
setLogsStarted: typeof setLogsStarted;
|
||||
messages: LogMessage[];
|
||||
logsStarted: boolean;
|
||||
}
|
||||
var c: any = null;
|
||||
|
||||
const ErrorLogs = ({
|
||||
classes,
|
||||
logMessageReceived,
|
||||
logResetMessages,
|
||||
setLogsStarted,
|
||||
messages,
|
||||
logsStarted,
|
||||
}: ILogs) => {
|
||||
const [filter, setFilter] = useState<string>("");
|
||||
const [nodes, setNodes] = useState<string[]>([""]);
|
||||
const [selectedNode, setSelectedNode] = useState<string>("all");
|
||||
const [selectedUserAgent, setSelectedUserAgent] =
|
||||
useState<string>("Select user agent");
|
||||
const [userAgents, setUserAgents] = useState<string[]>(["All User Agents"]);
|
||||
const [logType, setLogType] = useState<string>("all");
|
||||
const [loadingNodes, setLoadingNodes] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
const startLogs = () => {
|
||||
logResetMessages();
|
||||
const url = new URL(window.location.toString());
|
||||
const isDev = process.env.NODE_ENV === "development";
|
||||
@@ -97,14 +159,18 @@ const ErrorLogs = ({
|
||||
|
||||
const wsProt = wsProtocol(url.protocol);
|
||||
|
||||
const c = new W3CWebSocket(
|
||||
`${wsProt}://${url.hostname}:${port}/ws/console`
|
||||
c = new W3CWebSocket(
|
||||
`${wsProt}://${
|
||||
url.hostname
|
||||
}:${port}/ws/console/?logType=${logType}&node=${
|
||||
selectedNode === "Select node" ? "" : selectedNode
|
||||
}`
|
||||
);
|
||||
|
||||
let interval: any | null = null;
|
||||
if (c !== null) {
|
||||
c.onopen = () => {
|
||||
console.log("WebSocket Client Connected");
|
||||
setLogsStarted(true);
|
||||
c.send("ok");
|
||||
interval = setInterval(() => {
|
||||
c.send("ok");
|
||||
@@ -113,54 +179,205 @@ const ErrorLogs = ({
|
||||
c.onmessage = (message: IMessageEvent) => {
|
||||
// console.log(message.data.toString())
|
||||
// FORMAT: 00:35:17 UTC 01/01/2021
|
||||
|
||||
let m: LogMessage = JSON.parse(message.data.toString());
|
||||
m.time = moment(m.time, "HH:mm:s UTC MM/DD/YYYY").toDate();
|
||||
m.key = Math.random();
|
||||
if (userAgents.indexOf(m.userAgent) < 0 && m.userAgent !== undefined) {
|
||||
userAgents.push(m.userAgent);
|
||||
setUserAgents(userAgents);
|
||||
}
|
||||
logMessageReceived(m);
|
||||
};
|
||||
c.onclose = () => {
|
||||
clearInterval(interval);
|
||||
console.log("connection closed by server");
|
||||
setLogsStarted(false);
|
||||
};
|
||||
return () => {
|
||||
c.close(1000);
|
||||
clearInterval(interval);
|
||||
console.log("closing websockets");
|
||||
setLogsStarted(false);
|
||||
};
|
||||
}
|
||||
}, [logMessageReceived, logResetMessages]);
|
||||
};
|
||||
|
||||
const stopLogs = () => {
|
||||
if (c !== null && c !== undefined) {
|
||||
c.close(1000);
|
||||
setLogsStarted(false);
|
||||
}
|
||||
};
|
||||
|
||||
const filtLow = filter.toLowerCase();
|
||||
let filteredMessages = messages.filter((m) => {
|
||||
if (filter !== "") {
|
||||
if (m.ConsoleMsg.toLowerCase().indexOf(filtLow) >= 0) {
|
||||
return true;
|
||||
} else if (
|
||||
m.error &&
|
||||
m.error.source &&
|
||||
m.error.source.filter((x) => {
|
||||
return x.toLowerCase().indexOf(filtLow) >= 0;
|
||||
}).length > 0
|
||||
) {
|
||||
return true;
|
||||
} else if (
|
||||
m.error &&
|
||||
m.error.message.toLowerCase().indexOf(filtLow) >= 0
|
||||
) {
|
||||
return true;
|
||||
} else if (m.api && m.api.name.toLowerCase().indexOf(filtLow) >= 0) {
|
||||
return true;
|
||||
if (
|
||||
m.userAgent === selectedUserAgent ||
|
||||
selectedUserAgent === "All User Agents" ||
|
||||
selectedUserAgent === "Select user agent"
|
||||
) {
|
||||
if (filter !== "") {
|
||||
if (m.ConsoleMsg.toLowerCase().indexOf(filtLow) >= 0) {
|
||||
return true;
|
||||
} else if (
|
||||
m.error &&
|
||||
m.error.source &&
|
||||
m.error.source.filter((x) => {
|
||||
return x.toLowerCase().indexOf(filtLow) >= 0;
|
||||
}).length > 0
|
||||
) {
|
||||
return true;
|
||||
} else if (
|
||||
m.error &&
|
||||
m.error.message.toLowerCase().indexOf(filtLow) >= 0
|
||||
) {
|
||||
return true;
|
||||
} else if (m.api && m.api.name.toLowerCase().indexOf(filtLow) >= 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
return true;
|
||||
} else return false;
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setLoadingNodes(true);
|
||||
api
|
||||
.invoke("GET", `/api/v1/nodes`)
|
||||
.then((res: string[]) => {
|
||||
setNodes(res);
|
||||
// if (res.length > 0) {
|
||||
// setSelectedNode(res[0]);
|
||||
// }
|
||||
setLoadingNodes(false);
|
||||
})
|
||||
.catch((err: ErrorResponseHandler) => {
|
||||
setLoadingNodes(false);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<PageHeader label="Logs" />
|
||||
<PageLayout>
|
||||
<Grid container>
|
||||
<Grid container spacing={1}>
|
||||
<Grid item xs={4}>
|
||||
{!loadingNodes ? (
|
||||
<FormControl variant="outlined" className={classes.nodeField}>
|
||||
<Select
|
||||
id="node"
|
||||
name="node"
|
||||
data-test-id="node-selector"
|
||||
value={selectedNode}
|
||||
onChange={(e) => {
|
||||
setSelectedNode(e.target.value as string);
|
||||
}}
|
||||
className={classes.searchField}
|
||||
disabled={loadingNodes || logsStarted}
|
||||
input={<SelectStyled />}
|
||||
placeholder={"Select Node"}
|
||||
>
|
||||
<MenuItem value={"all"} key={`select-node-all`}>
|
||||
All Nodes
|
||||
</MenuItem>
|
||||
{nodes.map((aNode) => (
|
||||
<MenuItem value={aNode} key={`select-node-name-${aNode}`}>
|
||||
{aNode}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
) : (
|
||||
<h3> Loading nodes</h3>
|
||||
)}
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={3}>
|
||||
<FormControl variant="outlined" className={classes.nodeField}>
|
||||
<Select
|
||||
id="logType"
|
||||
name="logType"
|
||||
data-test-id="log-type"
|
||||
value={logType}
|
||||
onChange={(e) => {
|
||||
setLogType(e.target.value as string);
|
||||
}}
|
||||
className={classes.searchField}
|
||||
disabled={loadingNodes || logsStarted}
|
||||
input={<SelectStyled />}
|
||||
placeholder={"Select Log Type"}
|
||||
>
|
||||
<MenuItem value="all" key="all-log-types">
|
||||
All Log Types
|
||||
</MenuItem>
|
||||
<MenuItem value="minio" key="minio-log-type">
|
||||
MinIO
|
||||
</MenuItem>
|
||||
<MenuItem value="application" key="app-log-type">
|
||||
Application
|
||||
</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
<Grid item xs={3}>
|
||||
{userAgents.length > 1 && (
|
||||
<FormControl variant="outlined" className={classes.nodeField}>
|
||||
<Select
|
||||
id="userAgent"
|
||||
name="userAgent"
|
||||
data-test-id="user-agent"
|
||||
value={selectedUserAgent}
|
||||
onChange={(e) => {
|
||||
setSelectedUserAgent(e.target.value as string);
|
||||
}}
|
||||
className={classes.searchField}
|
||||
disabled={userAgents.length < 1 || logsStarted}
|
||||
input={<SelectStyled />}
|
||||
>
|
||||
<MenuItem
|
||||
value={selectedUserAgent}
|
||||
key={`select-user-agent-default`}
|
||||
disabled={true}
|
||||
>
|
||||
Select User Agent
|
||||
</MenuItem>
|
||||
{userAgents.map((anAgent) => (
|
||||
<MenuItem
|
||||
value={anAgent}
|
||||
key={`select-user-agent-${anAgent}`}
|
||||
>
|
||||
{anAgent}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
</Grid>
|
||||
<Grid item xs={2} textAlign={"right"}>
|
||||
{!logsStarted && (
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={false}
|
||||
onClick={startLogs}
|
||||
>
|
||||
Start Logs
|
||||
</Button>
|
||||
)}
|
||||
{logsStarted && (
|
||||
<Button
|
||||
type="button"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={stopLogs}
|
||||
>
|
||||
Stop Logs
|
||||
</Button>
|
||||
)}
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.actionsTray}>
|
||||
<SearchBox
|
||||
placeholder="Filter"
|
||||
@@ -170,38 +387,27 @@ const ErrorLogs = ({
|
||||
value={filter}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} data-test-id={"logs-list-container"}>
|
||||
{filteredMessages.length ? (
|
||||
<div id="logs-container" className={classes.logList}>
|
||||
<TableContainer
|
||||
component={Paper}
|
||||
sx={{
|
||||
borderBottom: "0px",
|
||||
}}
|
||||
>
|
||||
<Table aria-label="collapsible table">
|
||||
<TableBody>
|
||||
{filteredMessages.map((m) => {
|
||||
return <LogLine key={m.key} log={m} />;
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</div>
|
||||
) : (
|
||||
<Box
|
||||
sx={{
|
||||
padding: "15px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
{filter.trim().length
|
||||
? `No matching logs for "${filter.trim()}".`
|
||||
: "No logs to display."}
|
||||
</Box>
|
||||
)}
|
||||
<Grid item xs={12}>
|
||||
<div
|
||||
id="logs-container"
|
||||
className={classes.logList}
|
||||
data-test-id="logs-list-container"
|
||||
>
|
||||
<TableContainer component={Paper}>
|
||||
<Table aria-label="collapsible table">
|
||||
<TableBody>
|
||||
{filteredMessages.map((m) => {
|
||||
return <LogLine log={m} />;
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
{filteredMessages.length === 0 && (
|
||||
<div style={{ padding: 20, textAlign: "center" }}>
|
||||
No logs to display
|
||||
</div>
|
||||
)}
|
||||
</TableContainer>
|
||||
</div>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</PageLayout>
|
||||
@@ -210,12 +416,15 @@ const ErrorLogs = ({
|
||||
};
|
||||
|
||||
const mapState = (state: AppState) => ({
|
||||
messages: state.logs.messages,
|
||||
messages: state.logs.logMessages,
|
||||
logsStarted: state.logs.logsStarted,
|
||||
});
|
||||
|
||||
const connector = connect(mapState, {
|
||||
logMessageReceived: logMessageReceived,
|
||||
logResetMessages: logResetMessages,
|
||||
setLogsStarted,
|
||||
});
|
||||
|
||||
export default withStyles(styles)(connector(ErrorLogs));
|
||||
//export default withStyles(styles)(connector(ErrorLogs));
|
||||
export default connector(withStyles(styles)(ErrorLogs));
|
||||
|
||||
@@ -177,6 +177,20 @@ const LogLine = (props: { log: LogMessage }) => {
|
||||
{dateStr}
|
||||
</Box>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
onClick={() => setOpen(!open)}
|
||||
style={{ width: 200, color: "#989898", fontSize: 12 }}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
"& .min-icon": { width: 12, marginRight: 1 },
|
||||
fontWeight: "bold",
|
||||
lineHeight: 1,
|
||||
}}
|
||||
>
|
||||
{log.errKind}
|
||||
</Box>
|
||||
</TableCell>
|
||||
<TableCell onClick={() => setOpen(!open)}>
|
||||
<div
|
||||
style={{
|
||||
@@ -235,7 +249,7 @@ const LogLine = (props: { log: LogMessage }) => {
|
||||
<div style={{ marginTop: 10 }}>Log Details</div>
|
||||
</Collapse>
|
||||
</TableCell>
|
||||
<TableCell style={{ paddingBottom: 0, paddingTop: 0 }}>
|
||||
<TableCell colSpan={2} style={{ paddingBottom: 0, paddingTop: 0 }}>
|
||||
<Collapse in={open} timeout="auto" unmountOnExit>
|
||||
<Box sx={{ margin: 1 }}>
|
||||
<Typography
|
||||
|
||||
@@ -18,6 +18,7 @@ import { LogMessage } from "./types";
|
||||
|
||||
export const LOG_MESSAGE_RECEIVED = "LOG_MESSAGE_RECEIVED";
|
||||
export const LOG_RESET_MESSAGES = "LOG_RESET_MESSAGES";
|
||||
export const LOG_SET_STARTED = "LOG_SET_STARTED";
|
||||
|
||||
interface LogMessageReceivedAction {
|
||||
type: typeof LOG_MESSAGE_RECEIVED;
|
||||
@@ -28,7 +29,15 @@ interface LogResetMessagesAction {
|
||||
type: typeof LOG_RESET_MESSAGES;
|
||||
}
|
||||
|
||||
export type LogActionTypes = LogMessageReceivedAction | LogResetMessagesAction;
|
||||
interface LogSetStarted {
|
||||
type: typeof LOG_SET_STARTED;
|
||||
status: boolean;
|
||||
}
|
||||
|
||||
export type LogActionTypes =
|
||||
| LogMessageReceivedAction
|
||||
| LogResetMessagesAction
|
||||
| LogSetStarted;
|
||||
|
||||
export function logMessageReceived(message: LogMessage) {
|
||||
return {
|
||||
@@ -42,3 +51,10 @@ export function logResetMessages() {
|
||||
type: LOG_RESET_MESSAGES,
|
||||
};
|
||||
}
|
||||
|
||||
export function setLogsStarted(status: boolean) {
|
||||
return {
|
||||
type: LOG_SET_STARTED,
|
||||
status,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -17,16 +17,19 @@
|
||||
import {
|
||||
LOG_MESSAGE_RECEIVED,
|
||||
LOG_RESET_MESSAGES,
|
||||
LOG_SET_STARTED,
|
||||
LogActionTypes,
|
||||
} from "./actions";
|
||||
import { LogMessage } from "./types";
|
||||
|
||||
export interface LogState {
|
||||
messages: LogMessage[];
|
||||
logMessages: LogMessage[];
|
||||
logsStarted: boolean;
|
||||
}
|
||||
|
||||
const initialState: LogState = {
|
||||
messages: [],
|
||||
logMessages: [],
|
||||
logsStarted: false,
|
||||
};
|
||||
|
||||
export function logReducer(
|
||||
@@ -37,7 +40,7 @@ export function logReducer(
|
||||
case LOG_MESSAGE_RECEIVED:
|
||||
// if it's a simple ConsoleMsg, append it to the current ConsoleMsg in the
|
||||
// state if any
|
||||
let msgs = [...state.messages];
|
||||
let msgs = [...state.logMessages];
|
||||
|
||||
if (
|
||||
msgs.length > 0 &&
|
||||
@@ -57,12 +60,17 @@ export function logReducer(
|
||||
|
||||
return {
|
||||
...state,
|
||||
messages: msgs,
|
||||
logMessages: msgs,
|
||||
};
|
||||
case LOG_RESET_MESSAGES:
|
||||
return {
|
||||
...state,
|
||||
messages: [],
|
||||
logMessages: [],
|
||||
};
|
||||
case LOG_SET_STARTED:
|
||||
return {
|
||||
...state,
|
||||
logsStarted: action.status,
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
|
||||
@@ -41,4 +41,5 @@ export interface LogMessage {
|
||||
error: logError;
|
||||
ConsoleMsg: string;
|
||||
key: number;
|
||||
errKind: string;
|
||||
}
|
||||
|
||||
@@ -52,3 +52,11 @@ test("Log window exists in Logs page", async (t) => {
|
||||
.expect(logWindowExists)
|
||||
.ok();
|
||||
});
|
||||
|
||||
test("Node selector exists in Logs page", async (t) => {
|
||||
const nodeSelectorExists = elements.nodeSelector.exists;
|
||||
await t
|
||||
.navigateTo("http://localhost:9090/tools/logs")
|
||||
.expect(nodeSelectorExists)
|
||||
.ok();
|
||||
});
|
||||
|
||||
@@ -207,3 +207,5 @@ export const settingsAuditWebhookTab = Selector(".MuiTab-root").withAttribute(
|
||||
// Log window
|
||||
//----------------------------------------------------
|
||||
export const logWindow = Selector('[data-test-id="logs-list-container"]');
|
||||
//Node selector
|
||||
export const nodeSelector = Selector('[data-test-id="node-selector"]');
|
||||
|
||||
@@ -29,24 +29,38 @@ import (
|
||||
const logTimeFormat string = "15:04:05 MST 01/02/2006"
|
||||
|
||||
// startConsoleLog starts log of the servers
|
||||
func startConsoleLog(ctx context.Context, conn WSConn, client MinioAdmin) error {
|
||||
// TODO: accept parameters as variables
|
||||
func startConsoleLog(ctx context.Context, conn WSConn, client MinioAdmin, logRequest LogRequest) error {
|
||||
var node string
|
||||
// name of node, default = "" (all)
|
||||
node := ""
|
||||
if logRequest.node == "all" {
|
||||
node = ""
|
||||
} else {
|
||||
node = logRequest.node
|
||||
}
|
||||
|
||||
trimNode := strings.Split(node, ":")
|
||||
// number of log lines
|
||||
lineCount := 100
|
||||
// type of logs "minio"|"application"|"all" default = "all"
|
||||
logKind := "all"
|
||||
var logKind string
|
||||
if logRequest.logType == "minio" || logRequest.logType == "application" || logRequest.logType == "all" {
|
||||
logKind = logRequest.logType
|
||||
} else {
|
||||
logKind = "all"
|
||||
}
|
||||
|
||||
// Start listening on all Console Log activity.
|
||||
logCh := client.getLogs(ctx, node, lineCount, logKind)
|
||||
logCh := client.getLogs(ctx, trimNode[0], lineCount, logKind)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
case logInfo, ok := <-logCh:
|
||||
|
||||
// zero value returned because the channel is closed and empty
|
||||
if !ok {
|
||||
|
||||
return nil
|
||||
}
|
||||
if logInfo.Err != nil {
|
||||
|
||||
@@ -81,7 +81,7 @@ func TestAdminConsoleLog(t *testing.T) {
|
||||
writesCount++
|
||||
return nil
|
||||
}
|
||||
if err := startConsoleLog(ctx, mockWSConn, adminClient); err != nil {
|
||||
if err := startConsoleLog(ctx, mockWSConn, adminClient, LogRequest{node: "", logType: "all"}); err != nil {
|
||||
t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
|
||||
}
|
||||
// check that the TestReceiver got the same number of data from Console.
|
||||
@@ -93,7 +93,7 @@ func TestAdminConsoleLog(t *testing.T) {
|
||||
connWriteMessageMock = func(messageType int, data []byte) error {
|
||||
return fmt.Errorf("error on write")
|
||||
}
|
||||
if err := startConsoleLog(ctx, mockWSConn, adminClient); assert.Error(err) {
|
||||
if err := startConsoleLog(ctx, mockWSConn, adminClient, LogRequest{node: "", logType: "all"}); assert.Error(err) {
|
||||
assert.Equal("error on write", err.Error())
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ func TestAdminConsoleLog(t *testing.T) {
|
||||
connWriteMessageMock = func(messageType int, data []byte) error {
|
||||
return nil
|
||||
}
|
||||
if err := startConsoleLog(ctx, mockWSConn, adminClient); assert.Error(err) {
|
||||
if err := startConsoleLog(ctx, mockWSConn, adminClient, LogRequest{node: "", logType: "all"}); assert.Error(err) {
|
||||
assert.Equal("error on Console", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
55
restapi/admin_nodes.go
Normal file
55
restapi/admin_nodes.go
Normal file
@@ -0,0 +1,55 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2022 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package restapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/go-openapi/runtime/middleware"
|
||||
"github.com/minio/console/models"
|
||||
"github.com/minio/console/restapi/operations"
|
||||
"github.com/minio/console/restapi/operations/admin_api"
|
||||
)
|
||||
|
||||
func registerNodesHandler(api *operations.ConsoleAPI) {
|
||||
|
||||
api.AdminAPIListNodesHandler = admin_api.ListNodesHandlerFunc(func(params admin_api.ListNodesParams, session *models.Principal) middleware.Responder {
|
||||
listNodesResponse, err := getListNodesResponse(session)
|
||||
if err != nil {
|
||||
return admin_api.NewListNodesDefault(int(err.Code)).WithPayload(err)
|
||||
}
|
||||
return admin_api.NewListNodesOK().WithPayload(listNodesResponse)
|
||||
})
|
||||
}
|
||||
|
||||
// getListNodesResponse returns a list of available node endpoints .
|
||||
func getListNodesResponse(session *models.Principal) ([]string, *models.Error) {
|
||||
ctx := context.Background()
|
||||
mAdmin, err := NewMinioAdminClient(session)
|
||||
if err != nil {
|
||||
return nil, prepareError(err)
|
||||
}
|
||||
var nodeList []string
|
||||
|
||||
adminResources, _ := mAdmin.ServerInfo(ctx)
|
||||
|
||||
for _, n := range adminResources.Servers {
|
||||
nodeList = append(nodeList, n.Endpoint)
|
||||
}
|
||||
|
||||
return nodeList, nil
|
||||
}
|
||||
@@ -280,6 +280,7 @@ func (ac AdminClient) serviceTrace(ctx context.Context, threshold int64, s3, int
|
||||
|
||||
// implements madmin.GetLogs()
|
||||
func (ac AdminClient) getLogs(ctx context.Context, node string, lineCnt int, logKind string) <-chan madmin.LogInfo {
|
||||
|
||||
return ac.Client.GetLogs(ctx, node, lineCnt, logKind)
|
||||
}
|
||||
|
||||
|
||||
@@ -128,6 +128,8 @@ func configureAPI(api *operations.ConsoleAPI) http.Handler {
|
||||
registerAdminTiersHandlers(api)
|
||||
//Register Inspect Handler
|
||||
registerInspectHandler(api)
|
||||
// Register nodes handlers
|
||||
registerNodesHandler(api)
|
||||
|
||||
registerSiteReplicationHandler(api)
|
||||
|
||||
|
||||
@@ -2912,6 +2912,32 @@ func init() {
|
||||
}
|
||||
}
|
||||
},
|
||||
"/nodes": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"AdminAPI"
|
||||
],
|
||||
"summary": "Lists Nodes",
|
||||
"operationId": "ListNodes",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "Generic error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/policies": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@@ -9680,6 +9706,32 @@ func init() {
|
||||
}
|
||||
}
|
||||
},
|
||||
"/nodes": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"AdminAPI"
|
||||
],
|
||||
"summary": "Lists Nodes",
|
||||
"operationId": "ListNodes",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "Generic error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/policies": {
|
||||
"get": {
|
||||
"tags": [
|
||||
|
||||
88
restapi/operations/admin_api/list_nodes.go
Normal file
88
restapi/operations/admin_api/list_nodes.go
Normal file
@@ -0,0 +1,88 @@
|
||||
// Code generated by go-swagger; DO NOT EDIT.
|
||||
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2022 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
package admin_api
|
||||
|
||||
// This file was generated by the swagger tool.
|
||||
// Editing this file might prove futile when you re-run the generate command
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-openapi/runtime/middleware"
|
||||
|
||||
"github.com/minio/console/models"
|
||||
)
|
||||
|
||||
// ListNodesHandlerFunc turns a function with the right signature into a list nodes handler
|
||||
type ListNodesHandlerFunc func(ListNodesParams, *models.Principal) middleware.Responder
|
||||
|
||||
// Handle executing the request and returning a response
|
||||
func (fn ListNodesHandlerFunc) Handle(params ListNodesParams, principal *models.Principal) middleware.Responder {
|
||||
return fn(params, principal)
|
||||
}
|
||||
|
||||
// ListNodesHandler interface for that can handle valid list nodes params
|
||||
type ListNodesHandler interface {
|
||||
Handle(ListNodesParams, *models.Principal) middleware.Responder
|
||||
}
|
||||
|
||||
// NewListNodes creates a new http.Handler for the list nodes operation
|
||||
func NewListNodes(ctx *middleware.Context, handler ListNodesHandler) *ListNodes {
|
||||
return &ListNodes{Context: ctx, Handler: handler}
|
||||
}
|
||||
|
||||
/* ListNodes swagger:route GET /nodes AdminAPI listNodes
|
||||
|
||||
Lists Nodes
|
||||
|
||||
*/
|
||||
type ListNodes struct {
|
||||
Context *middleware.Context
|
||||
Handler ListNodesHandler
|
||||
}
|
||||
|
||||
func (o *ListNodes) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
||||
route, rCtx, _ := o.Context.RouteInfo(r)
|
||||
if rCtx != nil {
|
||||
*r = *rCtx
|
||||
}
|
||||
var Params = NewListNodesParams()
|
||||
uprinc, aCtx, err := o.Context.Authorize(r, route)
|
||||
if err != nil {
|
||||
o.Context.Respond(rw, r, route.Produces, route, err)
|
||||
return
|
||||
}
|
||||
if aCtx != nil {
|
||||
*r = *aCtx
|
||||
}
|
||||
var principal *models.Principal
|
||||
if uprinc != nil {
|
||||
principal = uprinc.(*models.Principal) // this is really a models.Principal, I promise
|
||||
}
|
||||
|
||||
if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params
|
||||
o.Context.Respond(rw, r, route.Produces, route, err)
|
||||
return
|
||||
}
|
||||
|
||||
res := o.Handler.Handle(Params, principal) // actually handle the request
|
||||
o.Context.Respond(rw, r, route.Produces, route, res)
|
||||
|
||||
}
|
||||
63
restapi/operations/admin_api/list_nodes_parameters.go
Normal file
63
restapi/operations/admin_api/list_nodes_parameters.go
Normal file
@@ -0,0 +1,63 @@
|
||||
// Code generated by go-swagger; DO NOT EDIT.
|
||||
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2022 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
package admin_api
|
||||
|
||||
// This file was generated by the swagger tool.
|
||||
// Editing this file might prove futile when you re-run the swagger generate command
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-openapi/errors"
|
||||
"github.com/go-openapi/runtime/middleware"
|
||||
)
|
||||
|
||||
// NewListNodesParams creates a new ListNodesParams object
|
||||
//
|
||||
// There are no default values defined in the spec.
|
||||
func NewListNodesParams() ListNodesParams {
|
||||
|
||||
return ListNodesParams{}
|
||||
}
|
||||
|
||||
// ListNodesParams contains all the bound params for the list nodes operation
|
||||
// typically these are obtained from a http.Request
|
||||
//
|
||||
// swagger:parameters ListNodes
|
||||
type ListNodesParams struct {
|
||||
|
||||
// HTTP Request Object
|
||||
HTTPRequest *http.Request `json:"-"`
|
||||
}
|
||||
|
||||
// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
|
||||
// for simple values it will use straight method calls.
|
||||
//
|
||||
// To ensure default values, the struct must have been initialized with NewListNodesParams() beforehand.
|
||||
func (o *ListNodesParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
|
||||
var res []error
|
||||
|
||||
o.HTTPRequest = r
|
||||
|
||||
if len(res) > 0 {
|
||||
return errors.CompositeValidationError(res...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
136
restapi/operations/admin_api/list_nodes_responses.go
Normal file
136
restapi/operations/admin_api/list_nodes_responses.go
Normal file
@@ -0,0 +1,136 @@
|
||||
// Code generated by go-swagger; DO NOT EDIT.
|
||||
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2022 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
package admin_api
|
||||
|
||||
// This file was generated by the swagger tool.
|
||||
// Editing this file might prove futile when you re-run the swagger generate command
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-openapi/runtime"
|
||||
|
||||
"github.com/minio/console/models"
|
||||
)
|
||||
|
||||
// ListNodesOKCode is the HTTP code returned for type ListNodesOK
|
||||
const ListNodesOKCode int = 200
|
||||
|
||||
/*ListNodesOK A successful response.
|
||||
|
||||
swagger:response listNodesOK
|
||||
*/
|
||||
type ListNodesOK struct {
|
||||
|
||||
/*
|
||||
In: Body
|
||||
*/
|
||||
Payload []string `json:"body,omitempty"`
|
||||
}
|
||||
|
||||
// NewListNodesOK creates ListNodesOK with default headers values
|
||||
func NewListNodesOK() *ListNodesOK {
|
||||
|
||||
return &ListNodesOK{}
|
||||
}
|
||||
|
||||
// WithPayload adds the payload to the list nodes o k response
|
||||
func (o *ListNodesOK) WithPayload(payload []string) *ListNodesOK {
|
||||
o.Payload = payload
|
||||
return o
|
||||
}
|
||||
|
||||
// SetPayload sets the payload to the list nodes o k response
|
||||
func (o *ListNodesOK) SetPayload(payload []string) {
|
||||
o.Payload = payload
|
||||
}
|
||||
|
||||
// WriteResponse to the client
|
||||
func (o *ListNodesOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
|
||||
|
||||
rw.WriteHeader(200)
|
||||
payload := o.Payload
|
||||
if payload == nil {
|
||||
// return empty array
|
||||
payload = make([]string, 0, 50)
|
||||
}
|
||||
|
||||
if err := producer.Produce(rw, payload); err != nil {
|
||||
panic(err) // let the recovery middleware deal with this
|
||||
}
|
||||
}
|
||||
|
||||
/*ListNodesDefault Generic error response.
|
||||
|
||||
swagger:response listNodesDefault
|
||||
*/
|
||||
type ListNodesDefault struct {
|
||||
_statusCode int
|
||||
|
||||
/*
|
||||
In: Body
|
||||
*/
|
||||
Payload *models.Error `json:"body,omitempty"`
|
||||
}
|
||||
|
||||
// NewListNodesDefault creates ListNodesDefault with default headers values
|
||||
func NewListNodesDefault(code int) *ListNodesDefault {
|
||||
if code <= 0 {
|
||||
code = 500
|
||||
}
|
||||
|
||||
return &ListNodesDefault{
|
||||
_statusCode: code,
|
||||
}
|
||||
}
|
||||
|
||||
// WithStatusCode adds the status to the list nodes default response
|
||||
func (o *ListNodesDefault) WithStatusCode(code int) *ListNodesDefault {
|
||||
o._statusCode = code
|
||||
return o
|
||||
}
|
||||
|
||||
// SetStatusCode sets the status to the list nodes default response
|
||||
func (o *ListNodesDefault) SetStatusCode(code int) {
|
||||
o._statusCode = code
|
||||
}
|
||||
|
||||
// WithPayload adds the payload to the list nodes default response
|
||||
func (o *ListNodesDefault) WithPayload(payload *models.Error) *ListNodesDefault {
|
||||
o.Payload = payload
|
||||
return o
|
||||
}
|
||||
|
||||
// SetPayload sets the payload to the list nodes default response
|
||||
func (o *ListNodesDefault) SetPayload(payload *models.Error) {
|
||||
o.Payload = payload
|
||||
}
|
||||
|
||||
// WriteResponse to the client
|
||||
func (o *ListNodesDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
|
||||
|
||||
rw.WriteHeader(o._statusCode)
|
||||
if o.Payload != nil {
|
||||
payload := o.Payload
|
||||
if err := producer.Produce(rw, payload); err != nil {
|
||||
panic(err) // let the recovery middleware deal with this
|
||||
}
|
||||
}
|
||||
}
|
||||
104
restapi/operations/admin_api/list_nodes_urlbuilder.go
Normal file
104
restapi/operations/admin_api/list_nodes_urlbuilder.go
Normal file
@@ -0,0 +1,104 @@
|
||||
// Code generated by go-swagger; DO NOT EDIT.
|
||||
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2022 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
package admin_api
|
||||
|
||||
// This file was generated by the swagger tool.
|
||||
// Editing this file might prove futile when you re-run the generate command
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
golangswaggerpaths "path"
|
||||
)
|
||||
|
||||
// ListNodesURL generates an URL for the list nodes operation
|
||||
type ListNodesURL struct {
|
||||
_basePath string
|
||||
}
|
||||
|
||||
// WithBasePath sets the base path for this url builder, only required when it's different from the
|
||||
// base path specified in the swagger spec.
|
||||
// When the value of the base path is an empty string
|
||||
func (o *ListNodesURL) WithBasePath(bp string) *ListNodesURL {
|
||||
o.SetBasePath(bp)
|
||||
return o
|
||||
}
|
||||
|
||||
// SetBasePath sets the base path for this url builder, only required when it's different from the
|
||||
// base path specified in the swagger spec.
|
||||
// When the value of the base path is an empty string
|
||||
func (o *ListNodesURL) SetBasePath(bp string) {
|
||||
o._basePath = bp
|
||||
}
|
||||
|
||||
// Build a url path and query string
|
||||
func (o *ListNodesURL) Build() (*url.URL, error) {
|
||||
var _result url.URL
|
||||
|
||||
var _path = "/nodes"
|
||||
|
||||
_basePath := o._basePath
|
||||
if _basePath == "" {
|
||||
_basePath = "/api/v1"
|
||||
}
|
||||
_result.Path = golangswaggerpaths.Join(_basePath, _path)
|
||||
|
||||
return &_result, nil
|
||||
}
|
||||
|
||||
// Must is a helper function to panic when the url builder returns an error
|
||||
func (o *ListNodesURL) Must(u *url.URL, err error) *url.URL {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if u == nil {
|
||||
panic("url can't be nil")
|
||||
}
|
||||
return u
|
||||
}
|
||||
|
||||
// String returns the string representation of the path with query string
|
||||
func (o *ListNodesURL) String() string {
|
||||
return o.Must(o.Build()).String()
|
||||
}
|
||||
|
||||
// BuildFull builds a full url with scheme, host, path and query string
|
||||
func (o *ListNodesURL) BuildFull(scheme, host string) (*url.URL, error) {
|
||||
if scheme == "" {
|
||||
return nil, errors.New("scheme is required for a full url on ListNodesURL")
|
||||
}
|
||||
if host == "" {
|
||||
return nil, errors.New("host is required for a full url on ListNodesURL")
|
||||
}
|
||||
|
||||
base, err := o.Build()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
base.Scheme = scheme
|
||||
base.Host = host
|
||||
return base, nil
|
||||
}
|
||||
|
||||
// StringFull returns the string representation of a complete url
|
||||
func (o *ListNodesURL) StringFull(scheme, host string) string {
|
||||
return o.Must(o.BuildFull(scheme, host)).String()
|
||||
}
|
||||
@@ -261,6 +261,9 @@ func NewConsoleAPI(spec *loads.Document) *ConsoleAPI {
|
||||
AdminAPIListGroupsForPolicyHandler: admin_api.ListGroupsForPolicyHandlerFunc(func(params admin_api.ListGroupsForPolicyParams, principal *models.Principal) middleware.Responder {
|
||||
return middleware.NotImplemented("operation admin_api.ListGroupsForPolicy has not yet been implemented")
|
||||
}),
|
||||
AdminAPIListNodesHandler: admin_api.ListNodesHandlerFunc(func(params admin_api.ListNodesParams, principal *models.Principal) middleware.Responder {
|
||||
return middleware.NotImplemented("operation admin_api.ListNodes has not yet been implemented")
|
||||
}),
|
||||
UserAPIListObjectsHandler: user_api.ListObjectsHandlerFunc(func(params user_api.ListObjectsParams, principal *models.Principal) middleware.Responder {
|
||||
return middleware.NotImplemented("operation user_api.ListObjects has not yet been implemented")
|
||||
}),
|
||||
@@ -612,6 +615,8 @@ type ConsoleAPI struct {
|
||||
AdminAPIListGroupsHandler admin_api.ListGroupsHandler
|
||||
// AdminAPIListGroupsForPolicyHandler sets the operation handler for the list groups for policy operation
|
||||
AdminAPIListGroupsForPolicyHandler admin_api.ListGroupsForPolicyHandler
|
||||
// AdminAPIListNodesHandler sets the operation handler for the list nodes operation
|
||||
AdminAPIListNodesHandler admin_api.ListNodesHandler
|
||||
// UserAPIListObjectsHandler sets the operation handler for the list objects operation
|
||||
UserAPIListObjectsHandler user_api.ListObjectsHandler
|
||||
// AdminAPIListPoliciesHandler sets the operation handler for the list policies operation
|
||||
@@ -1004,6 +1009,9 @@ func (o *ConsoleAPI) Validate() error {
|
||||
if o.AdminAPIListGroupsForPolicyHandler == nil {
|
||||
unregistered = append(unregistered, "admin_api.ListGroupsForPolicyHandler")
|
||||
}
|
||||
if o.AdminAPIListNodesHandler == nil {
|
||||
unregistered = append(unregistered, "admin_api.ListNodesHandler")
|
||||
}
|
||||
if o.UserAPIListObjectsHandler == nil {
|
||||
unregistered = append(unregistered, "user_api.ListObjectsHandler")
|
||||
}
|
||||
@@ -1532,6 +1540,10 @@ func (o *ConsoleAPI) initHandlerCache() {
|
||||
if o.handlers["GET"] == nil {
|
||||
o.handlers["GET"] = make(map[string]http.Handler)
|
||||
}
|
||||
o.handlers["GET"]["/nodes"] = admin_api.NewListNodes(o.context, o.AdminAPIListNodesHandler)
|
||||
if o.handlers["GET"] == nil {
|
||||
o.handlers["GET"] = make(map[string]http.Handler)
|
||||
}
|
||||
o.handlers["GET"]["/buckets/{bucket_name}/objects"] = user_api.NewListObjects(o.context, o.UserAPIListObjectsHandler)
|
||||
if o.handlers["GET"] == nil {
|
||||
o.handlers["GET"] = make(map[string]http.Handler)
|
||||
|
||||
@@ -97,6 +97,12 @@ type TraceRequest struct {
|
||||
path string
|
||||
}
|
||||
|
||||
//Type for log requests. This allows for filtering by node and kind
|
||||
type LogRequest struct {
|
||||
node string
|
||||
logType string
|
||||
}
|
||||
|
||||
func (c wsConn) writeMessage(messageType int, data []byte) error {
|
||||
return c.conn.WriteMessage(messageType, data)
|
||||
}
|
||||
@@ -168,12 +174,20 @@ func serveWS(w http.ResponseWriter, req *http.Request) {
|
||||
|
||||
go wsAdminClient.trace(traceRequestItem)
|
||||
case strings.HasPrefix(wsPath, `/console`):
|
||||
|
||||
wsAdminClient, err := newWebSocketAdminClient(conn, session)
|
||||
if err != nil {
|
||||
closeWsConn(conn)
|
||||
return
|
||||
}
|
||||
go wsAdminClient.console()
|
||||
node := req.URL.Query().Get("node")
|
||||
logType := req.URL.Query().Get("logType")
|
||||
|
||||
logRequestItem := LogRequest{
|
||||
node: node,
|
||||
logType: logType,
|
||||
}
|
||||
go wsAdminClient.console(logRequestItem)
|
||||
case strings.HasPrefix(wsPath, `/health-info`):
|
||||
deadline, err := getHealthInfoOptionsFromReq(req)
|
||||
if err != nil {
|
||||
@@ -330,7 +344,7 @@ func (wsc *wsAdminClient) trace(traceRequestItem TraceRequest) {
|
||||
|
||||
// console serves madmin.GetLogs
|
||||
// on a Websocket connection.
|
||||
func (wsc *wsAdminClient) console() {
|
||||
func (wsc *wsAdminClient) console(logRequestItem LogRequest) {
|
||||
defer func() {
|
||||
LogInfo("console logs stopped")
|
||||
// close connection after return
|
||||
@@ -340,7 +354,7 @@ func (wsc *wsAdminClient) console() {
|
||||
|
||||
ctx := wsReadClientCtx(wsc.conn)
|
||||
|
||||
err := startConsoleLog(ctx, wsc.conn, wsc.client)
|
||||
err := startConsoleLog(ctx, wsc.conn, wsc.client, logRequestItem)
|
||||
|
||||
sendWsCloseMessage(wsc.conn, err)
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ securityDefinitions:
|
||||
tokenUrl: http://min.io
|
||||
# Apply the key security definition to all APIs
|
||||
security:
|
||||
- key: [ ]
|
||||
- key: []
|
||||
paths:
|
||||
/login:
|
||||
get:
|
||||
@@ -35,7 +35,7 @@ paths:
|
||||
schema:
|
||||
$ref: "#/definitions/error"
|
||||
# Exclude this API from the authentication requirement
|
||||
security: [ ]
|
||||
security: []
|
||||
tags:
|
||||
- UserAPI
|
||||
post:
|
||||
@@ -55,7 +55,7 @@ paths:
|
||||
schema:
|
||||
$ref: "#/definitions/error"
|
||||
# Exclude this API from the authentication requirement
|
||||
security: [ ]
|
||||
security: []
|
||||
tags:
|
||||
- UserAPI
|
||||
/login/oauth2/auth:
|
||||
@@ -75,7 +75,7 @@ paths:
|
||||
description: Generic error response.
|
||||
schema:
|
||||
$ref: "#/definitions/error"
|
||||
security: [ ]
|
||||
security: []
|
||||
tags:
|
||||
- UserAPI
|
||||
|
||||
@@ -122,7 +122,7 @@ paths:
|
||||
description: Generic error response.
|
||||
schema:
|
||||
$ref: "#/definitions/error"
|
||||
security: [ ]
|
||||
security: []
|
||||
tags:
|
||||
- UserAPI
|
||||
|
||||
@@ -2600,6 +2600,24 @@ paths:
|
||||
tags:
|
||||
- AdminAPI
|
||||
|
||||
/nodes:
|
||||
get:
|
||||
summary: Lists Nodes
|
||||
operationId: ListNodes
|
||||
responses:
|
||||
200:
|
||||
description: A successful response.
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
default:
|
||||
description: Generic error response.
|
||||
schema:
|
||||
$ref: "#/definitions/error"
|
||||
tags:
|
||||
- AdminAPI
|
||||
|
||||
/remote-buckets:
|
||||
get:
|
||||
summary: List Remote Buckets
|
||||
@@ -2701,7 +2719,7 @@ paths:
|
||||
- name: order
|
||||
in: query
|
||||
type: string
|
||||
enum: [ timeDesc, timeAsc ]
|
||||
enum: [timeDesc, timeAsc]
|
||||
default: timeDesc
|
||||
- name: timeStart
|
||||
in: query
|
||||
@@ -3515,7 +3533,7 @@ definitions:
|
||||
properties:
|
||||
loginStrategy:
|
||||
type: string
|
||||
enum: [ form, redirect, service-account ]
|
||||
enum: [form, redirect, service-account]
|
||||
redirect:
|
||||
type: string
|
||||
loginOauth2AuthRequest:
|
||||
@@ -3598,7 +3616,7 @@ definitions:
|
||||
type: string
|
||||
status:
|
||||
type: string
|
||||
enum: [ ok ]
|
||||
enum: [ok]
|
||||
operator:
|
||||
type: boolean
|
||||
distributedMode:
|
||||
@@ -3619,7 +3637,7 @@ definitions:
|
||||
type: string
|
||||
values:
|
||||
type: array
|
||||
items: { }
|
||||
items: {}
|
||||
resultTarget:
|
||||
type: object
|
||||
properties:
|
||||
@@ -3985,7 +4003,7 @@ definitions:
|
||||
type: string
|
||||
service:
|
||||
type: string
|
||||
enum: [ replication ]
|
||||
enum: [replication]
|
||||
syncMode:
|
||||
type: string
|
||||
bandwidth:
|
||||
|
||||
Reference in New Issue
Block a user