mirror of
https://github.com/OneUptime/oneuptime.git
synced 2026-04-06 00:32:12 +02:00
feat: Add SCIM logging functionality for projects and status pages
- Implemented ProjectSCIMLog and StatusPageSCIMLog models to store SCIM operation logs. - Created services for managing ProjectSCIMLog and StatusPageSCIMLog entries with automatic deletion of old logs. - Developed SCIMLogger utility for creating logs with sanitized sensitive data. - Added SCIMLogStatus enum to represent the status of SCIM operations. - Introduced ProjectSCIMLogsTable and StatusPageSCIMLogsTable components for displaying logs in the dashboard. - Enhanced logging with detailed request/response information and error handling.
This commit is contained in:
@@ -2,6 +2,8 @@ import SCIMMiddleware from "Common/Server/Middleware/SCIMAuthorization";
|
||||
import UserService from "Common/Server/Services/UserService";
|
||||
import TeamMemberService from "Common/Server/Services/TeamMemberService";
|
||||
import TeamService from "Common/Server/Services/TeamService";
|
||||
import { createProjectSCIMLog } from "../Utils/SCIMLogger";
|
||||
import SCIMLogStatus from "Common/Types/SCIM/SCIMLogStatus";
|
||||
import Express, {
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
@@ -1063,12 +1065,44 @@ router.post(
|
||||
`Project SCIM Bulk - completed processing ${results.length} operations with ${errorCount} errors`,
|
||||
);
|
||||
|
||||
const bulkResponse: JSONObject = generateBulkResponse(results);
|
||||
|
||||
// Log the bulk operation
|
||||
void createProjectSCIMLog({
|
||||
projectId: projectId,
|
||||
projectScimId: new ObjectID(projectScimId),
|
||||
operationType: "BulkOperation",
|
||||
status: errorCount > 0 ? SCIMLogStatus.Warning : SCIMLogStatus.Success,
|
||||
statusMessage: `Processed ${results.length} operations with ${errorCount} errors`,
|
||||
httpMethod: "POST",
|
||||
requestPath: req.path,
|
||||
httpStatusCode: 200,
|
||||
requestBody: req.body,
|
||||
responseBody: bulkResponse,
|
||||
});
|
||||
|
||||
return Response.sendJsonObjectResponse(
|
||||
req,
|
||||
res,
|
||||
generateBulkResponse(results),
|
||||
bulkResponse,
|
||||
);
|
||||
} catch (err) {
|
||||
// Log the error
|
||||
const oneuptimeRequestErr: OneUptimeRequest = req as OneUptimeRequest;
|
||||
const bearerDataErr: JSONObject =
|
||||
oneuptimeRequestErr.bearerTokenData as JSONObject;
|
||||
void createProjectSCIMLog({
|
||||
projectId: bearerDataErr["projectId"] as ObjectID,
|
||||
projectScimId: new ObjectID(req.params["projectScimId"]!),
|
||||
operationType: "BulkOperation",
|
||||
status: SCIMLogStatus.Error,
|
||||
statusMessage: (err as Error).message,
|
||||
httpMethod: "POST",
|
||||
requestPath: req.path,
|
||||
httpStatusCode: 500,
|
||||
requestBody: req.body,
|
||||
});
|
||||
|
||||
logger.error(err);
|
||||
return next(err);
|
||||
}
|
||||
@@ -1249,12 +1283,41 @@ router.get(
|
||||
|
||||
logger.debug(`SCIM Users response prepared with ${users.length} users`);
|
||||
|
||||
const responseBody: JSONObject = generateUsersListResponse(paginatedUsers, startIndex, users.length);
|
||||
|
||||
// Log the operation
|
||||
void createProjectSCIMLog({
|
||||
projectId: projectId,
|
||||
projectScimId: new ObjectID(req.params["projectScimId"]!),
|
||||
operationType: "ListUsers",
|
||||
status: SCIMLogStatus.Success,
|
||||
httpMethod: "GET",
|
||||
requestPath: req.path,
|
||||
httpStatusCode: 200,
|
||||
responseBody: responseBody,
|
||||
});
|
||||
|
||||
return Response.sendJsonObjectResponse(
|
||||
req,
|
||||
res,
|
||||
generateUsersListResponse(paginatedUsers, startIndex, users.length),
|
||||
responseBody,
|
||||
);
|
||||
} catch (err) {
|
||||
// Log the error
|
||||
const oneuptimeRequest: OneUptimeRequest = req as OneUptimeRequest;
|
||||
const bearerData: JSONObject =
|
||||
oneuptimeRequest.bearerTokenData as JSONObject;
|
||||
void createProjectSCIMLog({
|
||||
projectId: bearerData["projectId"] as ObjectID,
|
||||
projectScimId: new ObjectID(req.params["projectScimId"]!),
|
||||
operationType: "ListUsers",
|
||||
status: SCIMLogStatus.Error,
|
||||
statusMessage: (err as Error).message,
|
||||
httpMethod: "GET",
|
||||
requestPath: req.path,
|
||||
httpStatusCode: 500,
|
||||
});
|
||||
|
||||
logger.error(err);
|
||||
return next(err);
|
||||
}
|
||||
@@ -1325,8 +1388,36 @@ router.get(
|
||||
"project",
|
||||
);
|
||||
|
||||
// Log the operation
|
||||
void createProjectSCIMLog({
|
||||
projectId: projectId,
|
||||
projectScimId: new ObjectID(req.params["projectScimId"]!),
|
||||
operationType: "GetUser",
|
||||
status: SCIMLogStatus.Success,
|
||||
httpMethod: "GET",
|
||||
requestPath: req.path,
|
||||
httpStatusCode: 200,
|
||||
affectedUserEmail: projectUser.user.email?.toString(),
|
||||
responseBody: user,
|
||||
});
|
||||
|
||||
return Response.sendJsonObjectResponse(req, res, user);
|
||||
} catch (err) {
|
||||
// Log the error
|
||||
const oneuptimeRequest: OneUptimeRequest = req as OneUptimeRequest;
|
||||
const bearerData: JSONObject =
|
||||
oneuptimeRequest.bearerTokenData as JSONObject;
|
||||
void createProjectSCIMLog({
|
||||
projectId: bearerData["projectId"] as ObjectID,
|
||||
projectScimId: new ObjectID(req.params["projectScimId"]!),
|
||||
operationType: "GetUser",
|
||||
status: SCIMLogStatus.Error,
|
||||
statusMessage: (err as Error).message,
|
||||
httpMethod: "GET",
|
||||
requestPath: req.path,
|
||||
httpStatusCode: 404,
|
||||
});
|
||||
|
||||
logger.error(err);
|
||||
return next(err);
|
||||
}
|
||||
@@ -1475,6 +1566,21 @@ const handleUserUpdate: (
|
||||
req.params["projectScimId"]!,
|
||||
"project",
|
||||
);
|
||||
|
||||
// Log the operation
|
||||
void createProjectSCIMLog({
|
||||
projectId: projectId,
|
||||
projectScimId: new ObjectID(req.params["projectScimId"]!),
|
||||
operationType: "UpdateUser",
|
||||
status: SCIMLogStatus.Success,
|
||||
httpMethod: req.method,
|
||||
requestPath: req.path,
|
||||
httpStatusCode: 200,
|
||||
affectedUserEmail: email,
|
||||
requestBody: scimUser,
|
||||
responseBody: user,
|
||||
});
|
||||
|
||||
return Response.sendJsonObjectResponse(req, res, user);
|
||||
}
|
||||
}
|
||||
@@ -1489,8 +1595,38 @@ const handleUserUpdate: (
|
||||
"project",
|
||||
);
|
||||
|
||||
// Log the operation
|
||||
void createProjectSCIMLog({
|
||||
projectId: projectId,
|
||||
projectScimId: new ObjectID(req.params["projectScimId"]!),
|
||||
operationType: "UpdateUser",
|
||||
status: SCIMLogStatus.Success,
|
||||
httpMethod: req.method,
|
||||
requestPath: req.path,
|
||||
httpStatusCode: 200,
|
||||
affectedUserEmail: projectUser.user.email?.toString(),
|
||||
requestBody: scimUser,
|
||||
responseBody: user,
|
||||
});
|
||||
|
||||
return Response.sendJsonObjectResponse(req, res, user);
|
||||
} catch (err) {
|
||||
// Log the error
|
||||
const oneuptimeRequest: OneUptimeRequest = req as OneUptimeRequest;
|
||||
const bearerData: JSONObject =
|
||||
oneuptimeRequest.bearerTokenData as JSONObject;
|
||||
void createProjectSCIMLog({
|
||||
projectId: bearerData["projectId"] as ObjectID,
|
||||
projectScimId: new ObjectID(req.params["projectScimId"]!),
|
||||
operationType: "UpdateUser",
|
||||
status: SCIMLogStatus.Error,
|
||||
statusMessage: (err as Error).message,
|
||||
httpMethod: req.method,
|
||||
requestPath: req.path,
|
||||
httpStatusCode: 400,
|
||||
requestBody: req.body,
|
||||
});
|
||||
|
||||
logger.error(err);
|
||||
return next(err);
|
||||
}
|
||||
@@ -1589,14 +1725,43 @@ router.get(
|
||||
`SCIM Groups response prepared with ${groups.length} groups`,
|
||||
);
|
||||
|
||||
return Response.sendJsonObjectResponse(req, res, {
|
||||
const responseBody: JSONObject = {
|
||||
schemas: ["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
|
||||
totalResults: groups.length,
|
||||
startIndex: startIndex,
|
||||
itemsPerPage: paginatedGroups.length,
|
||||
Resources: paginatedGroups,
|
||||
};
|
||||
|
||||
// Log the operation
|
||||
void createProjectSCIMLog({
|
||||
projectId: projectId,
|
||||
projectScimId: new ObjectID(req.params["projectScimId"]!),
|
||||
operationType: "ListGroups",
|
||||
status: SCIMLogStatus.Success,
|
||||
httpMethod: "GET",
|
||||
requestPath: req.path,
|
||||
httpStatusCode: 200,
|
||||
responseBody: responseBody,
|
||||
});
|
||||
|
||||
return Response.sendJsonObjectResponse(req, res, responseBody);
|
||||
} catch (err) {
|
||||
// Log the error
|
||||
const oneuptimeRequest: OneUptimeRequest = req as OneUptimeRequest;
|
||||
const bearerData: JSONObject =
|
||||
oneuptimeRequest.bearerTokenData as JSONObject;
|
||||
void createProjectSCIMLog({
|
||||
projectId: bearerData["projectId"] as ObjectID,
|
||||
projectScimId: new ObjectID(req.params["projectScimId"]!),
|
||||
operationType: "ListGroups",
|
||||
status: SCIMLogStatus.Error,
|
||||
statusMessage: (err as Error).message,
|
||||
httpMethod: "GET",
|
||||
requestPath: req.path,
|
||||
httpStatusCode: 500,
|
||||
});
|
||||
|
||||
logger.error(err);
|
||||
return next(err);
|
||||
}
|
||||
@@ -1663,8 +1828,36 @@ router.get(
|
||||
true, // Include members for individual group request
|
||||
);
|
||||
|
||||
// Log the operation
|
||||
void createProjectSCIMLog({
|
||||
projectId: projectId,
|
||||
projectScimId: new ObjectID(req.params["projectScimId"]!),
|
||||
operationType: "GetGroup",
|
||||
status: SCIMLogStatus.Success,
|
||||
httpMethod: "GET",
|
||||
requestPath: req.path,
|
||||
httpStatusCode: 200,
|
||||
affectedGroupName: team.name?.toString(),
|
||||
responseBody: group,
|
||||
});
|
||||
|
||||
return Response.sendJsonObjectResponse(req, res, group);
|
||||
} catch (err) {
|
||||
// Log the error
|
||||
const oneuptimeRequest: OneUptimeRequest = req as OneUptimeRequest;
|
||||
const bearerData: JSONObject =
|
||||
oneuptimeRequest.bearerTokenData as JSONObject;
|
||||
void createProjectSCIMLog({
|
||||
projectId: bearerData["projectId"] as ObjectID,
|
||||
projectScimId: new ObjectID(req.params["projectScimId"]!),
|
||||
operationType: "GetGroup",
|
||||
status: SCIMLogStatus.Error,
|
||||
statusMessage: (err as Error).message,
|
||||
httpMethod: "GET",
|
||||
requestPath: req.path,
|
||||
httpStatusCode: 404,
|
||||
});
|
||||
|
||||
logger.error(err);
|
||||
return next(err);
|
||||
}
|
||||
@@ -1826,6 +2019,20 @@ router.post(
|
||||
`SCIM Create group - returning group with id: ${teamForResponse.id}`,
|
||||
);
|
||||
|
||||
// Log the operation
|
||||
void createProjectSCIMLog({
|
||||
projectId: projectId,
|
||||
projectScimId: new ObjectID(req.params["projectScimId"]!),
|
||||
operationType: "CreateGroup",
|
||||
status: SCIMLogStatus.Success,
|
||||
httpMethod: "POST",
|
||||
requestPath: req.path,
|
||||
httpStatusCode: createdNewTeam ? 201 : 200,
|
||||
affectedGroupName: displayName,
|
||||
requestBody: scimGroup,
|
||||
responseBody: groupResponse,
|
||||
});
|
||||
|
||||
if (createdNewTeam) {
|
||||
res.status(201);
|
||||
} else {
|
||||
@@ -1834,6 +2041,22 @@ router.post(
|
||||
|
||||
return Response.sendJsonObjectResponse(req, res, groupResponse);
|
||||
} catch (err) {
|
||||
// Log the error
|
||||
const oneuptimeRequest: OneUptimeRequest = req as OneUptimeRequest;
|
||||
const bearerData: JSONObject =
|
||||
oneuptimeRequest.bearerTokenData as JSONObject;
|
||||
void createProjectSCIMLog({
|
||||
projectId: bearerData["projectId"] as ObjectID,
|
||||
projectScimId: new ObjectID(req.params["projectScimId"]!),
|
||||
operationType: "CreateGroup",
|
||||
status: SCIMLogStatus.Error,
|
||||
statusMessage: (err as Error).message,
|
||||
httpMethod: "POST",
|
||||
requestPath: req.path,
|
||||
httpStatusCode: 400,
|
||||
requestBody: req.body,
|
||||
});
|
||||
|
||||
logger.error(err);
|
||||
return next(err);
|
||||
}
|
||||
@@ -1992,11 +2215,42 @@ router.put(
|
||||
req.params["projectScimId"]!,
|
||||
true,
|
||||
);
|
||||
|
||||
// Log the operation
|
||||
void createProjectSCIMLog({
|
||||
projectId: projectId,
|
||||
projectScimId: new ObjectID(req.params["projectScimId"]!),
|
||||
operationType: "UpdateGroup",
|
||||
status: SCIMLogStatus.Success,
|
||||
httpMethod: "PUT",
|
||||
requestPath: req.path,
|
||||
httpStatusCode: 200,
|
||||
affectedGroupName: displayName || updatedTeam.name?.toString(),
|
||||
requestBody: scimGroup,
|
||||
responseBody: updatedGroup,
|
||||
});
|
||||
|
||||
return Response.sendJsonObjectResponse(req, res, updatedGroup);
|
||||
}
|
||||
|
||||
throw new NotFoundException("Failed to retrieve updated group");
|
||||
} catch (err) {
|
||||
// Log the error
|
||||
const oneuptimeRequest: OneUptimeRequest = req as OneUptimeRequest;
|
||||
const bearerData: JSONObject =
|
||||
oneuptimeRequest.bearerTokenData as JSONObject;
|
||||
void createProjectSCIMLog({
|
||||
projectId: bearerData["projectId"] as ObjectID,
|
||||
projectScimId: new ObjectID(req.params["projectScimId"]!),
|
||||
operationType: "UpdateGroup",
|
||||
status: SCIMLogStatus.Error,
|
||||
statusMessage: (err as Error).message,
|
||||
httpMethod: "PUT",
|
||||
requestPath: req.path,
|
||||
httpStatusCode: 400,
|
||||
requestBody: req.body,
|
||||
});
|
||||
|
||||
logger.error(err);
|
||||
return next(err);
|
||||
}
|
||||
@@ -2083,11 +2337,38 @@ router.delete(
|
||||
|
||||
logger.debug(`SCIM Delete group - team successfully deleted`);
|
||||
|
||||
// Log the operation
|
||||
void createProjectSCIMLog({
|
||||
projectId: projectId,
|
||||
projectScimId: new ObjectID(req.params["projectScimId"]!),
|
||||
operationType: "DeleteGroup",
|
||||
status: SCIMLogStatus.Success,
|
||||
httpMethod: "DELETE",
|
||||
requestPath: req.path,
|
||||
httpStatusCode: 204,
|
||||
affectedGroupName: team.name?.toString(),
|
||||
});
|
||||
|
||||
res.status(204);
|
||||
return Response.sendJsonObjectResponse(req, res, {
|
||||
message: "Group deleted",
|
||||
});
|
||||
} catch (err) {
|
||||
// Log the error
|
||||
const oneuptimeRequest: OneUptimeRequest = req as OneUptimeRequest;
|
||||
const bearerData: JSONObject =
|
||||
oneuptimeRequest.bearerTokenData as JSONObject;
|
||||
void createProjectSCIMLog({
|
||||
projectId: bearerData["projectId"] as ObjectID,
|
||||
projectScimId: new ObjectID(req.params["projectScimId"]!),
|
||||
operationType: "DeleteGroup",
|
||||
status: SCIMLogStatus.Error,
|
||||
statusMessage: (err as Error).message,
|
||||
httpMethod: "DELETE",
|
||||
requestPath: req.path,
|
||||
httpStatusCode: 400,
|
||||
});
|
||||
|
||||
logger.error(err);
|
||||
return next(err);
|
||||
}
|
||||
@@ -2327,11 +2608,42 @@ router.patch(
|
||||
req.params["projectScimId"]!,
|
||||
true,
|
||||
);
|
||||
|
||||
// Log the operation
|
||||
void createProjectSCIMLog({
|
||||
projectId: projectId,
|
||||
projectScimId: new ObjectID(req.params["projectScimId"]!),
|
||||
operationType: "UpdateGroup",
|
||||
status: SCIMLogStatus.Success,
|
||||
httpMethod: "PATCH",
|
||||
requestPath: req.path,
|
||||
httpStatusCode: 200,
|
||||
affectedGroupName: updatedTeam.name?.toString(),
|
||||
requestBody: scimPatch,
|
||||
responseBody: updatedGroup,
|
||||
});
|
||||
|
||||
return Response.sendJsonObjectResponse(req, res, updatedGroup);
|
||||
}
|
||||
|
||||
throw new NotFoundException("Failed to retrieve updated group");
|
||||
} catch (err) {
|
||||
// Log the error
|
||||
const oneuptimeRequest: OneUptimeRequest = req as OneUptimeRequest;
|
||||
const bearerData: JSONObject =
|
||||
oneuptimeRequest.bearerTokenData as JSONObject;
|
||||
void createProjectSCIMLog({
|
||||
projectId: bearerData["projectId"] as ObjectID,
|
||||
projectScimId: new ObjectID(req.params["projectScimId"]!),
|
||||
operationType: "UpdateGroup",
|
||||
status: SCIMLogStatus.Error,
|
||||
statusMessage: (err as Error).message,
|
||||
httpMethod: "PATCH",
|
||||
requestPath: req.path,
|
||||
httpStatusCode: 400,
|
||||
requestBody: req.body,
|
||||
});
|
||||
|
||||
logger.error(err);
|
||||
return next(err);
|
||||
}
|
||||
@@ -2429,9 +2741,39 @@ router.post(
|
||||
`SCIM Create user - returning created user with id: ${user.id}`,
|
||||
);
|
||||
|
||||
// Log the operation
|
||||
void createProjectSCIMLog({
|
||||
projectId: projectId,
|
||||
projectScimId: new ObjectID(req.params["projectScimId"]!),
|
||||
operationType: "CreateUser",
|
||||
status: SCIMLogStatus.Success,
|
||||
httpMethod: "POST",
|
||||
requestPath: req.path,
|
||||
httpStatusCode: 201,
|
||||
affectedUserEmail: email,
|
||||
requestBody: scimUser,
|
||||
responseBody: createdUser,
|
||||
});
|
||||
|
||||
res.status(201);
|
||||
return Response.sendJsonObjectResponse(req, res, createdUser);
|
||||
} catch (err) {
|
||||
// Log the error
|
||||
const oneuptimeRequest: OneUptimeRequest = req as OneUptimeRequest;
|
||||
const bearerData: JSONObject =
|
||||
oneuptimeRequest.bearerTokenData as JSONObject;
|
||||
void createProjectSCIMLog({
|
||||
projectId: bearerData["projectId"] as ObjectID,
|
||||
projectScimId: new ObjectID(req.params["projectScimId"]!),
|
||||
operationType: "CreateUser",
|
||||
status: SCIMLogStatus.Error,
|
||||
statusMessage: (err as Error).message,
|
||||
httpMethod: "POST",
|
||||
requestPath: req.path,
|
||||
httpStatusCode: 400,
|
||||
requestBody: req.body,
|
||||
});
|
||||
|
||||
logger.error(err);
|
||||
return next(err);
|
||||
}
|
||||
@@ -2492,11 +2834,37 @@ router.delete(
|
||||
`SCIM Delete user - user successfully deprovisioned from project`,
|
||||
);
|
||||
|
||||
// Log the operation
|
||||
void createProjectSCIMLog({
|
||||
projectId: projectId,
|
||||
projectScimId: new ObjectID(req.params["projectScimId"]!),
|
||||
operationType: "DeleteUser",
|
||||
status: SCIMLogStatus.Success,
|
||||
httpMethod: "DELETE",
|
||||
requestPath: req.path,
|
||||
httpStatusCode: 204,
|
||||
});
|
||||
|
||||
res.status(204);
|
||||
return Response.sendJsonObjectResponse(req, res, {
|
||||
message: "User deprovisioned",
|
||||
});
|
||||
} catch (err) {
|
||||
// Log the error
|
||||
const oneuptimeRequest: OneUptimeRequest = req as OneUptimeRequest;
|
||||
const bearerData: JSONObject =
|
||||
oneuptimeRequest.bearerTokenData as JSONObject;
|
||||
void createProjectSCIMLog({
|
||||
projectId: bearerData["projectId"] as ObjectID,
|
||||
projectScimId: new ObjectID(req.params["projectScimId"]!),
|
||||
operationType: "DeleteUser",
|
||||
status: SCIMLogStatus.Error,
|
||||
statusMessage: (err as Error).message,
|
||||
httpMethod: "DELETE",
|
||||
requestPath: req.path,
|
||||
httpStatusCode: 400,
|
||||
});
|
||||
|
||||
logger.error(err);
|
||||
return next(err);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import SCIMMiddleware from "Common/Server/Middleware/SCIMAuthorization";
|
||||
import StatusPagePrivateUserService from "Common/Server/Services/StatusPagePrivateUserService";
|
||||
import { createStatusPageSCIMLog } from "../Utils/SCIMLogger";
|
||||
import SCIMLogStatus from "Common/Types/SCIM/SCIMLogStatus";
|
||||
import Express, {
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
@@ -443,12 +445,46 @@ router.post(
|
||||
`Status Page SCIM Bulk - completed processing ${results.length} operations with ${errorCount} errors`,
|
||||
);
|
||||
|
||||
const bulkResponse: JSONObject = generateBulkResponse(results);
|
||||
|
||||
// Log the bulk operation
|
||||
void createStatusPageSCIMLog({
|
||||
projectId: projectId,
|
||||
statusPageId: statusPageId,
|
||||
statusPageScimId: new ObjectID(statusPageScimId),
|
||||
operationType: "BulkOperation",
|
||||
status: errorCount > 0 ? SCIMLogStatus.Warning : SCIMLogStatus.Success,
|
||||
statusMessage: `Processed ${results.length} operations with ${errorCount} errors`,
|
||||
httpMethod: "POST",
|
||||
requestPath: req.path,
|
||||
httpStatusCode: 200,
|
||||
requestBody: req.body,
|
||||
responseBody: bulkResponse,
|
||||
});
|
||||
|
||||
return Response.sendJsonObjectResponse(
|
||||
req,
|
||||
res,
|
||||
generateBulkResponse(results),
|
||||
bulkResponse,
|
||||
);
|
||||
} catch (err) {
|
||||
// Log the error
|
||||
const oneuptimeRequestErr: OneUptimeRequest = req as OneUptimeRequest;
|
||||
const bearerDataErr: JSONObject =
|
||||
oneuptimeRequestErr.bearerTokenData as JSONObject;
|
||||
void createStatusPageSCIMLog({
|
||||
projectId: bearerDataErr["projectId"] as ObjectID,
|
||||
statusPageId: bearerDataErr["statusPageId"] as ObjectID,
|
||||
statusPageScimId: new ObjectID(req.params["statusPageScimId"]!),
|
||||
operationType: "BulkOperation",
|
||||
status: SCIMLogStatus.Error,
|
||||
statusMessage: (err as Error).message,
|
||||
httpMethod: "POST",
|
||||
requestPath: req.path,
|
||||
httpStatusCode: 500,
|
||||
requestBody: req.body,
|
||||
});
|
||||
|
||||
logger.error(err);
|
||||
return next(err);
|
||||
}
|
||||
@@ -565,14 +601,45 @@ router.get(
|
||||
`Status Page SCIM Users response prepared with ${users.length} users`,
|
||||
);
|
||||
|
||||
return Response.sendJsonObjectResponse(req, res, {
|
||||
const responseBody: JSONObject = {
|
||||
schemas: ["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
|
||||
totalResults: users.length,
|
||||
startIndex: startIndex,
|
||||
itemsPerPage: paginatedUsers.length,
|
||||
Resources: paginatedUsers,
|
||||
};
|
||||
|
||||
// Log the operation
|
||||
void createStatusPageSCIMLog({
|
||||
projectId: bearerData["projectId"] as ObjectID,
|
||||
statusPageId: statusPageId,
|
||||
statusPageScimId: new ObjectID(req.params["statusPageScimId"]!),
|
||||
operationType: "ListUsers",
|
||||
status: SCIMLogStatus.Success,
|
||||
httpMethod: "GET",
|
||||
requestPath: req.path,
|
||||
httpStatusCode: 200,
|
||||
responseBody: responseBody,
|
||||
});
|
||||
|
||||
return Response.sendJsonObjectResponse(req, res, responseBody);
|
||||
} catch (err) {
|
||||
// Log the error
|
||||
const oneuptimeRequest: OneUptimeRequest = req as OneUptimeRequest;
|
||||
const bearerData: JSONObject =
|
||||
oneuptimeRequest.bearerTokenData as JSONObject;
|
||||
void createStatusPageSCIMLog({
|
||||
projectId: bearerData["projectId"] as ObjectID,
|
||||
statusPageId: bearerData["statusPageId"] as ObjectID,
|
||||
statusPageScimId: new ObjectID(req.params["statusPageScimId"]!),
|
||||
operationType: "ListUsers",
|
||||
status: SCIMLogStatus.Error,
|
||||
statusMessage: (err as Error).message,
|
||||
httpMethod: "GET",
|
||||
requestPath: req.path,
|
||||
httpStatusCode: 500,
|
||||
});
|
||||
|
||||
logger.error(err);
|
||||
return next(err);
|
||||
}
|
||||
@@ -642,8 +709,38 @@ router.get(
|
||||
`Status Page SCIM Get user - returning user with id: ${statusPageUser.id}`,
|
||||
);
|
||||
|
||||
// Log the operation
|
||||
void createStatusPageSCIMLog({
|
||||
projectId: bearerData["projectId"] as ObjectID,
|
||||
statusPageId: statusPageId,
|
||||
statusPageScimId: new ObjectID(req.params["statusPageScimId"]!),
|
||||
operationType: "GetUser",
|
||||
status: SCIMLogStatus.Success,
|
||||
httpMethod: "GET",
|
||||
requestPath: req.path,
|
||||
httpStatusCode: 200,
|
||||
affectedUserEmail: statusPageUser.email?.toString(),
|
||||
responseBody: user,
|
||||
});
|
||||
|
||||
return Response.sendJsonObjectResponse(req, res, user);
|
||||
} catch (err) {
|
||||
// Log the error
|
||||
const oneuptimeRequest: OneUptimeRequest = req as OneUptimeRequest;
|
||||
const bearerData: JSONObject =
|
||||
oneuptimeRequest.bearerTokenData as JSONObject;
|
||||
void createStatusPageSCIMLog({
|
||||
projectId: bearerData["projectId"] as ObjectID,
|
||||
statusPageId: bearerData["statusPageId"] as ObjectID,
|
||||
statusPageScimId: new ObjectID(req.params["statusPageScimId"]!),
|
||||
operationType: "GetUser",
|
||||
status: SCIMLogStatus.Error,
|
||||
statusMessage: (err as Error).message,
|
||||
httpMethod: "GET",
|
||||
requestPath: req.path,
|
||||
httpStatusCode: 404,
|
||||
});
|
||||
|
||||
logger.error(err);
|
||||
return next(err);
|
||||
}
|
||||
@@ -747,9 +844,41 @@ router.post(
|
||||
`Status Page SCIM Create user - returning created user with id: ${user.id}`,
|
||||
);
|
||||
|
||||
// Log the operation
|
||||
void createStatusPageSCIMLog({
|
||||
projectId: bearerData["projectId"] as ObjectID,
|
||||
statusPageId: statusPageId,
|
||||
statusPageScimId: new ObjectID(req.params["statusPageScimId"]!),
|
||||
operationType: "CreateUser",
|
||||
status: SCIMLogStatus.Success,
|
||||
httpMethod: "POST",
|
||||
requestPath: req.path,
|
||||
httpStatusCode: 201,
|
||||
affectedUserEmail: email,
|
||||
requestBody: scimUser,
|
||||
responseBody: createdUser,
|
||||
});
|
||||
|
||||
res.status(201);
|
||||
return Response.sendJsonObjectResponse(req, res, createdUser);
|
||||
} catch (err) {
|
||||
// Log the error
|
||||
const oneuptimeRequest: OneUptimeRequest = req as OneUptimeRequest;
|
||||
const bearerData: JSONObject =
|
||||
oneuptimeRequest.bearerTokenData as JSONObject;
|
||||
void createStatusPageSCIMLog({
|
||||
projectId: bearerData["projectId"] as ObjectID,
|
||||
statusPageId: bearerData["statusPageId"] as ObjectID,
|
||||
statusPageScimId: new ObjectID(req.params["statusPageScimId"]!),
|
||||
operationType: "CreateUser",
|
||||
status: SCIMLogStatus.Error,
|
||||
statusMessage: (err as Error).message,
|
||||
httpMethod: "POST",
|
||||
requestPath: req.path,
|
||||
httpStatusCode: 400,
|
||||
requestBody: req.body,
|
||||
});
|
||||
|
||||
logger.error(err);
|
||||
return next(err);
|
||||
}
|
||||
@@ -890,6 +1019,22 @@ const handleStatusPageUserUpdate: (
|
||||
req.params["statusPageScimId"]!,
|
||||
"status-page",
|
||||
);
|
||||
|
||||
// Log the operation
|
||||
void createStatusPageSCIMLog({
|
||||
projectId: bearerData["projectId"] as ObjectID,
|
||||
statusPageId: statusPageId,
|
||||
statusPageScimId: new ObjectID(req.params["statusPageScimId"]!),
|
||||
operationType: "UpdateUser",
|
||||
status: SCIMLogStatus.Success,
|
||||
httpMethod: req.method,
|
||||
requestPath: req.path,
|
||||
httpStatusCode: 200,
|
||||
affectedUserEmail: email,
|
||||
requestBody: scimUser,
|
||||
responseBody: user,
|
||||
});
|
||||
|
||||
return Response.sendJsonObjectResponse(req, res, user);
|
||||
}
|
||||
}
|
||||
@@ -906,8 +1051,40 @@ const handleStatusPageUserUpdate: (
|
||||
"status-page",
|
||||
);
|
||||
|
||||
// Log the operation
|
||||
void createStatusPageSCIMLog({
|
||||
projectId: bearerData["projectId"] as ObjectID,
|
||||
statusPageId: statusPageId,
|
||||
statusPageScimId: new ObjectID(req.params["statusPageScimId"]!),
|
||||
operationType: "UpdateUser",
|
||||
status: SCIMLogStatus.Success,
|
||||
httpMethod: req.method,
|
||||
requestPath: req.path,
|
||||
httpStatusCode: 200,
|
||||
affectedUserEmail: statusPageUser.email?.toString(),
|
||||
requestBody: scimUser,
|
||||
responseBody: user,
|
||||
});
|
||||
|
||||
return Response.sendJsonObjectResponse(req, res, user);
|
||||
} catch (err) {
|
||||
// Log the error
|
||||
const oneuptimeRequest: OneUptimeRequest = req as OneUptimeRequest;
|
||||
const bearerData: JSONObject =
|
||||
oneuptimeRequest.bearerTokenData as JSONObject;
|
||||
void createStatusPageSCIMLog({
|
||||
projectId: bearerData["projectId"] as ObjectID,
|
||||
statusPageId: bearerData["statusPageId"] as ObjectID,
|
||||
statusPageScimId: new ObjectID(req.params["statusPageScimId"]!),
|
||||
operationType: "UpdateUser",
|
||||
status: SCIMLogStatus.Error,
|
||||
statusMessage: (err as Error).message,
|
||||
httpMethod: req.method,
|
||||
requestPath: req.path,
|
||||
httpStatusCode: 400,
|
||||
requestBody: req.body,
|
||||
});
|
||||
|
||||
logger.error(err);
|
||||
return next(err);
|
||||
}
|
||||
@@ -994,10 +1171,38 @@ router.delete(
|
||||
`Status Page SCIM Delete user - user deleted successfully for userId: ${userId}`,
|
||||
);
|
||||
|
||||
// Log the operation
|
||||
void createStatusPageSCIMLog({
|
||||
projectId: bearerData["projectId"] as ObjectID,
|
||||
statusPageId: statusPageId,
|
||||
statusPageScimId: new ObjectID(req.params["statusPageScimId"]!),
|
||||
operationType: "DeleteUser",
|
||||
status: SCIMLogStatus.Success,
|
||||
httpMethod: "DELETE",
|
||||
requestPath: req.path,
|
||||
httpStatusCode: 204,
|
||||
});
|
||||
|
||||
// Return 204 No Content for successful deletion
|
||||
res.status(204);
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
} catch (err) {
|
||||
// Log the error
|
||||
const oneuptimeRequest: OneUptimeRequest = req as OneUptimeRequest;
|
||||
const bearerData: JSONObject =
|
||||
oneuptimeRequest.bearerTokenData as JSONObject;
|
||||
void createStatusPageSCIMLog({
|
||||
projectId: bearerData["projectId"] as ObjectID,
|
||||
statusPageId: bearerData["statusPageId"] as ObjectID,
|
||||
statusPageScimId: new ObjectID(req.params["statusPageScimId"]!),
|
||||
operationType: "DeleteUser",
|
||||
status: SCIMLogStatus.Error,
|
||||
statusMessage: (err as Error).message,
|
||||
httpMethod: "DELETE",
|
||||
requestPath: req.path,
|
||||
httpStatusCode: 400,
|
||||
});
|
||||
|
||||
logger.error(err);
|
||||
return next(err);
|
||||
}
|
||||
|
||||
161
App/FeatureSet/Identity/Utils/SCIMLogger.ts
Normal file
161
App/FeatureSet/Identity/Utils/SCIMLogger.ts
Normal file
@@ -0,0 +1,161 @@
|
||||
import ProjectSCIMLog from "Common/Models/DatabaseModels/ProjectSCIMLog";
|
||||
import StatusPageSCIMLog from "Common/Models/DatabaseModels/StatusPageSCIMLog";
|
||||
import ProjectSCIMLogService from "Common/Server/Services/ProjectSCIMLogService";
|
||||
import StatusPageSCIMLogService from "Common/Server/Services/StatusPageSCIMLogService";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import SCIMLogStatus from "Common/Types/SCIM/SCIMLogStatus";
|
||||
import { JSONObject, JSONValue, JSONArray } from "Common/Types/JSON";
|
||||
|
||||
export interface ProjectSCIMLogData {
|
||||
projectId: ObjectID;
|
||||
projectScimId: ObjectID;
|
||||
operationType: string;
|
||||
status: SCIMLogStatus;
|
||||
statusMessage?: string;
|
||||
httpMethod?: string;
|
||||
requestPath?: string;
|
||||
httpStatusCode?: number;
|
||||
affectedUserEmail?: string;
|
||||
affectedGroupName?: string;
|
||||
requestBody?: JSONObject;
|
||||
responseBody?: JSONObject;
|
||||
}
|
||||
|
||||
export interface StatusPageSCIMLogData {
|
||||
projectId: ObjectID;
|
||||
statusPageId: ObjectID;
|
||||
statusPageScimId: ObjectID;
|
||||
operationType: string;
|
||||
status: SCIMLogStatus;
|
||||
statusMessage?: string;
|
||||
httpMethod?: string;
|
||||
requestPath?: string;
|
||||
httpStatusCode?: number;
|
||||
affectedUserEmail?: string;
|
||||
requestBody?: JSONObject;
|
||||
responseBody?: JSONObject;
|
||||
}
|
||||
|
||||
const sanitizeSensitiveData = (data: JSONObject | undefined): JSONObject | undefined => {
|
||||
if (!data) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const sanitized: JSONObject = { ...data };
|
||||
const sensitiveKeys: string[] = [
|
||||
"password",
|
||||
"bearerToken",
|
||||
"bearer_token",
|
||||
"authorization",
|
||||
"Authorization",
|
||||
"token",
|
||||
"secret",
|
||||
"apiKey",
|
||||
"api_key",
|
||||
];
|
||||
|
||||
const sanitizeRecursive = (obj: JSONObject): JSONObject => {
|
||||
const result: JSONObject = {};
|
||||
for (const key in obj) {
|
||||
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
||||
const value: JSONValue = obj[key];
|
||||
if (sensitiveKeys.some((k: string) => key.toLowerCase().includes(k.toLowerCase()))) {
|
||||
result[key] = "[REDACTED]";
|
||||
} else if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
||||
result[key] = sanitizeRecursive(value as JSONObject);
|
||||
} else if (Array.isArray(value)) {
|
||||
result[key] = (value as JSONArray).map((item: JSONValue) => {
|
||||
if (typeof item === "object" && item !== null && !Array.isArray(item)) {
|
||||
return sanitizeRecursive(item as JSONObject);
|
||||
}
|
||||
return item;
|
||||
}) as JSONArray;
|
||||
} else {
|
||||
result[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
return sanitizeRecursive(sanitized);
|
||||
};
|
||||
|
||||
const buildLogBody = (data: {
|
||||
requestBody?: JSONObject;
|
||||
responseBody?: JSONObject;
|
||||
timestamp: Date;
|
||||
}): string => {
|
||||
const logBody: JSONObject = {
|
||||
timestamp: data.timestamp.toISOString(),
|
||||
request: sanitizeSensitiveData(data.requestBody),
|
||||
response: sanitizeSensitiveData(data.responseBody),
|
||||
};
|
||||
return JSON.stringify(logBody);
|
||||
};
|
||||
|
||||
export const createProjectSCIMLog = async (data: ProjectSCIMLogData): Promise<void> => {
|
||||
try {
|
||||
const log: ProjectSCIMLog = new ProjectSCIMLog();
|
||||
log.projectId = data.projectId;
|
||||
log.projectScimId = data.projectScimId;
|
||||
log.operationType = data.operationType;
|
||||
log.status = data.status;
|
||||
log.statusMessage = data.statusMessage;
|
||||
log.httpMethod = data.httpMethod;
|
||||
log.requestPath = data.requestPath;
|
||||
log.httpStatusCode = data.httpStatusCode;
|
||||
log.affectedUserEmail = data.affectedUserEmail;
|
||||
log.affectedGroupName = data.affectedGroupName;
|
||||
log.logBody = buildLogBody({
|
||||
requestBody: data.requestBody,
|
||||
responseBody: data.responseBody,
|
||||
timestamp: new Date(),
|
||||
});
|
||||
|
||||
await ProjectSCIMLogService.create({
|
||||
data: log,
|
||||
props: { isRoot: true },
|
||||
});
|
||||
} catch (err) {
|
||||
// Log errors silently to not affect SCIM operations
|
||||
logger.error("Failed to create Project SCIM log entry:");
|
||||
logger.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
export const createStatusPageSCIMLog = async (data: StatusPageSCIMLogData): Promise<void> => {
|
||||
try {
|
||||
const log: StatusPageSCIMLog = new StatusPageSCIMLog();
|
||||
log.projectId = data.projectId;
|
||||
log.statusPageId = data.statusPageId;
|
||||
log.statusPageScimId = data.statusPageScimId;
|
||||
log.operationType = data.operationType;
|
||||
log.status = data.status;
|
||||
log.statusMessage = data.statusMessage;
|
||||
log.httpMethod = data.httpMethod;
|
||||
log.requestPath = data.requestPath;
|
||||
log.httpStatusCode = data.httpStatusCode;
|
||||
log.affectedUserEmail = data.affectedUserEmail;
|
||||
log.logBody = buildLogBody({
|
||||
requestBody: data.requestBody,
|
||||
responseBody: data.responseBody,
|
||||
timestamp: new Date(),
|
||||
});
|
||||
|
||||
await StatusPageSCIMLogService.create({
|
||||
data: log,
|
||||
props: { isRoot: true },
|
||||
});
|
||||
} catch (err) {
|
||||
// Log errors silently to not affect SCIM operations
|
||||
logger.error("Failed to create Status Page SCIM log entry:");
|
||||
logger.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
createProjectSCIMLog,
|
||||
createStatusPageSCIMLog,
|
||||
};
|
||||
@@ -197,6 +197,8 @@ import OnCallDutyPolicyUserOverride from "./OnCallDutyPolicyUserOverride";
|
||||
import MonitorFeed from "./MonitorFeed";
|
||||
import MetricType from "./MetricType";
|
||||
import ProjectSCIM from "./ProjectSCIM";
|
||||
import ProjectSCIMLog from "./ProjectSCIMLog";
|
||||
import StatusPageSCIMLog from "./StatusPageSCIMLog";
|
||||
|
||||
const AllModelTypes: Array<{
|
||||
new (): BaseModel;
|
||||
@@ -417,6 +419,8 @@ const AllModelTypes: Array<{
|
||||
OnCallDutyPolicyTimeLog,
|
||||
|
||||
ProjectSCIM,
|
||||
ProjectSCIMLog,
|
||||
StatusPageSCIMLog,
|
||||
];
|
||||
|
||||
const modelTypeMap: { [key: string]: { new (): BaseModel } } = {};
|
||||
|
||||
422
Common/Models/DatabaseModels/ProjectSCIMLog.ts
Normal file
422
Common/Models/DatabaseModels/ProjectSCIMLog.ts
Normal file
@@ -0,0 +1,422 @@
|
||||
import Project from "./Project";
|
||||
import ProjectSCIM from "./ProjectSCIM";
|
||||
import User from "./User";
|
||||
import BaseModel from "./DatabaseBaseModel/DatabaseBaseModel";
|
||||
import Route from "../../Types/API/Route";
|
||||
import ColumnAccessControl from "../../Types/Database/AccessControl/ColumnAccessControl";
|
||||
import TableAccessControl from "../../Types/Database/AccessControl/TableAccessControl";
|
||||
import ColumnLength from "../../Types/Database/ColumnLength";
|
||||
import ColumnType from "../../Types/Database/ColumnType";
|
||||
import CrudApiEndpoint from "../../Types/Database/CrudApiEndpoint";
|
||||
import EnableDocumentation from "../../Types/Database/EnableDocumentation";
|
||||
import TableColumn from "../../Types/Database/TableColumn";
|
||||
import TableColumnType from "../../Types/Database/TableColumnType";
|
||||
import TableMetadata from "../../Types/Database/TableMetadata";
|
||||
import TenantColumn from "../../Types/Database/TenantColumn";
|
||||
import IconProp from "../../Types/Icon/IconProp";
|
||||
import ObjectID from "../../Types/ObjectID";
|
||||
import Permission from "../../Types/Permission";
|
||||
import SCIMLogStatus from "../../Types/SCIM/SCIMLogStatus";
|
||||
import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm";
|
||||
|
||||
@EnableDocumentation()
|
||||
@TenantColumn("projectId")
|
||||
@TableAccessControl({
|
||||
create: [],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectSCIMLog,
|
||||
],
|
||||
delete: [],
|
||||
update: [],
|
||||
})
|
||||
@CrudApiEndpoint(new Route("/project-scim-log"))
|
||||
@Entity({
|
||||
name: "ProjectSCIMLog",
|
||||
})
|
||||
@TableMetadata({
|
||||
tableName: "ProjectSCIMLog",
|
||||
singularName: "SCIM Log",
|
||||
pluralName: "SCIM Logs",
|
||||
icon: IconProp.Terminal,
|
||||
tableDescription:
|
||||
"Logs of all SCIM provisioning operations for this project.",
|
||||
})
|
||||
export default class ProjectSCIMLog extends BaseModel {
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectSCIMLog,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
manyToOneRelationColumn: "projectId",
|
||||
type: TableColumnType.Entity,
|
||||
modelType: Project,
|
||||
title: "Project",
|
||||
description: "Relation to Project Resource in which this object belongs",
|
||||
})
|
||||
@ManyToOne(
|
||||
() => {
|
||||
return Project;
|
||||
},
|
||||
{
|
||||
eager: false,
|
||||
nullable: true,
|
||||
onDelete: "CASCADE",
|
||||
orphanedRowAction: "nullify",
|
||||
},
|
||||
)
|
||||
@JoinColumn({ name: "projectId" })
|
||||
public project?: Project = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectSCIMLog,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@Index()
|
||||
@TableColumn({
|
||||
type: TableColumnType.ObjectID,
|
||||
required: true,
|
||||
canReadOnRelationQuery: true,
|
||||
title: "Project ID",
|
||||
description: "ID of your OneUptime Project in which this object belongs",
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.ObjectID,
|
||||
nullable: false,
|
||||
transformer: ObjectID.getDatabaseTransformer(),
|
||||
})
|
||||
public projectId?: ObjectID = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectSCIMLog,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
manyToOneRelationColumn: "projectScimId",
|
||||
type: TableColumnType.Entity,
|
||||
modelType: ProjectSCIM,
|
||||
title: "Project SCIM",
|
||||
description: "Relation to ProjectSCIM Resource in which this log belongs",
|
||||
})
|
||||
@ManyToOne(
|
||||
() => {
|
||||
return ProjectSCIM;
|
||||
},
|
||||
{
|
||||
eager: false,
|
||||
nullable: true,
|
||||
onDelete: "CASCADE",
|
||||
orphanedRowAction: "nullify",
|
||||
},
|
||||
)
|
||||
@JoinColumn({ name: "projectScimId" })
|
||||
public projectScim?: ProjectSCIM = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectSCIMLog,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@Index()
|
||||
@TableColumn({
|
||||
type: TableColumnType.ObjectID,
|
||||
required: true,
|
||||
canReadOnRelationQuery: true,
|
||||
title: "Project SCIM ID",
|
||||
description: "ID of your Project SCIM configuration",
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.ObjectID,
|
||||
nullable: false,
|
||||
transformer: ObjectID.getDatabaseTransformer(),
|
||||
})
|
||||
public projectScimId?: ObjectID = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectSCIMLog,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
required: true,
|
||||
type: TableColumnType.ShortText,
|
||||
title: "Operation Type",
|
||||
description:
|
||||
"Type of SCIM operation (e.g., CreateUser, UpdateUser, DeleteUser, ListUsers, GetUser, CreateGroup, UpdateGroup, DeleteGroup, ListGroups, GetGroup, BulkOperation)",
|
||||
canReadOnRelationQuery: true,
|
||||
})
|
||||
@Column({
|
||||
nullable: false,
|
||||
type: ColumnType.ShortText,
|
||||
length: ColumnLength.ShortText,
|
||||
})
|
||||
public operationType?: string = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectSCIMLog,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
required: true,
|
||||
type: TableColumnType.ShortText,
|
||||
title: "Status",
|
||||
description: "Status of the SCIM operation",
|
||||
canReadOnRelationQuery: true,
|
||||
})
|
||||
@Column({
|
||||
nullable: false,
|
||||
type: ColumnType.ShortText,
|
||||
length: ColumnLength.ShortText,
|
||||
})
|
||||
public status?: SCIMLogStatus = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectSCIMLog,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
required: false,
|
||||
type: TableColumnType.LongText,
|
||||
title: "Status Message",
|
||||
description: "Short error or status description",
|
||||
canReadOnRelationQuery: false,
|
||||
})
|
||||
@Column({
|
||||
nullable: true,
|
||||
type: ColumnType.LongText,
|
||||
length: ColumnLength.LongText,
|
||||
})
|
||||
public statusMessage?: string = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectSCIMLog,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
required: false,
|
||||
type: TableColumnType.VeryLongText,
|
||||
title: "Log Body",
|
||||
description: "Detailed JSON with request/response data",
|
||||
canReadOnRelationQuery: false,
|
||||
})
|
||||
@Column({
|
||||
nullable: true,
|
||||
type: ColumnType.VeryLongText,
|
||||
})
|
||||
public logBody?: string = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectSCIMLog,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
required: false,
|
||||
type: TableColumnType.ShortText,
|
||||
title: "HTTP Method",
|
||||
description: "HTTP method used (GET, POST, PUT, PATCH, DELETE)",
|
||||
canReadOnRelationQuery: true,
|
||||
})
|
||||
@Column({
|
||||
nullable: true,
|
||||
type: ColumnType.ShortText,
|
||||
length: ColumnLength.ShortText,
|
||||
})
|
||||
public httpMethod?: string = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectSCIMLog,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
required: false,
|
||||
type: TableColumnType.LongText,
|
||||
title: "Request Path",
|
||||
description: "The SCIM endpoint path",
|
||||
canReadOnRelationQuery: false,
|
||||
})
|
||||
@Column({
|
||||
nullable: true,
|
||||
type: ColumnType.LongText,
|
||||
length: ColumnLength.LongText,
|
||||
})
|
||||
public requestPath?: string = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectSCIMLog,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
required: false,
|
||||
type: TableColumnType.Number,
|
||||
title: "HTTP Status Code",
|
||||
description: "Response HTTP status code",
|
||||
canReadOnRelationQuery: true,
|
||||
})
|
||||
@Column({
|
||||
nullable: true,
|
||||
type: ColumnType.Number,
|
||||
})
|
||||
public httpStatusCode?: number = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectSCIMLog,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@Index()
|
||||
@TableColumn({
|
||||
required: false,
|
||||
type: TableColumnType.Email,
|
||||
title: "Affected User Email",
|
||||
description: "Email of the user affected by this operation",
|
||||
canReadOnRelationQuery: true,
|
||||
})
|
||||
@Column({
|
||||
nullable: true,
|
||||
type: ColumnType.Email,
|
||||
length: ColumnLength.Email,
|
||||
})
|
||||
public affectedUserEmail?: string = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectSCIMLog,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@Index()
|
||||
@TableColumn({
|
||||
required: false,
|
||||
type: TableColumnType.ShortText,
|
||||
title: "Affected Group Name",
|
||||
description: "Name of the group/team affected by this operation",
|
||||
canReadOnRelationQuery: true,
|
||||
})
|
||||
@Column({
|
||||
nullable: true,
|
||||
type: ColumnType.ShortText,
|
||||
length: ColumnLength.ShortText,
|
||||
})
|
||||
public affectedGroupName?: string = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
manyToOneRelationColumn: "deletedByUserId",
|
||||
type: TableColumnType.Entity,
|
||||
title: "Deleted by User",
|
||||
modelType: User,
|
||||
description:
|
||||
"Relation to User who deleted this object (if this object was deleted by a User)",
|
||||
})
|
||||
@ManyToOne(
|
||||
() => {
|
||||
return User;
|
||||
},
|
||||
{
|
||||
cascade: false,
|
||||
eager: false,
|
||||
nullable: true,
|
||||
onDelete: "SET NULL",
|
||||
orphanedRowAction: "nullify",
|
||||
},
|
||||
)
|
||||
@JoinColumn({ name: "deletedByUserId" })
|
||||
public deletedByUser?: User = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
type: TableColumnType.ObjectID,
|
||||
title: "Deleted by User ID",
|
||||
description:
|
||||
"User ID who deleted this object (if this object was deleted by a User)",
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.ObjectID,
|
||||
nullable: true,
|
||||
transformer: ObjectID.getDatabaseTransformer(),
|
||||
})
|
||||
public deletedByUserId?: ObjectID = undefined;
|
||||
}
|
||||
455
Common/Models/DatabaseModels/StatusPageSCIMLog.ts
Normal file
455
Common/Models/DatabaseModels/StatusPageSCIMLog.ts
Normal file
@@ -0,0 +1,455 @@
|
||||
import Project from "./Project";
|
||||
import StatusPage from "./StatusPage";
|
||||
import StatusPageSCIM from "./StatusPageSCIM";
|
||||
import User from "./User";
|
||||
import BaseModel from "./DatabaseBaseModel/DatabaseBaseModel";
|
||||
import Route from "../../Types/API/Route";
|
||||
import ColumnAccessControl from "../../Types/Database/AccessControl/ColumnAccessControl";
|
||||
import TableAccessControl from "../../Types/Database/AccessControl/TableAccessControl";
|
||||
import ColumnLength from "../../Types/Database/ColumnLength";
|
||||
import ColumnType from "../../Types/Database/ColumnType";
|
||||
import CrudApiEndpoint from "../../Types/Database/CrudApiEndpoint";
|
||||
import EnableDocumentation from "../../Types/Database/EnableDocumentation";
|
||||
import TableColumn from "../../Types/Database/TableColumn";
|
||||
import TableColumnType from "../../Types/Database/TableColumnType";
|
||||
import TableMetadata from "../../Types/Database/TableMetadata";
|
||||
import TenantColumn from "../../Types/Database/TenantColumn";
|
||||
import IconProp from "../../Types/Icon/IconProp";
|
||||
import ObjectID from "../../Types/ObjectID";
|
||||
import Permission from "../../Types/Permission";
|
||||
import SCIMLogStatus from "../../Types/SCIM/SCIMLogStatus";
|
||||
import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm";
|
||||
|
||||
@EnableDocumentation()
|
||||
@TenantColumn("projectId")
|
||||
@TableAccessControl({
|
||||
create: [],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadStatusPageSCIMLog,
|
||||
],
|
||||
delete: [],
|
||||
update: [],
|
||||
})
|
||||
@CrudApiEndpoint(new Route("/status-page-scim-log"))
|
||||
@Entity({
|
||||
name: "StatusPageSCIMLog",
|
||||
})
|
||||
@TableMetadata({
|
||||
tableName: "StatusPageSCIMLog",
|
||||
singularName: "Status Page SCIM Log",
|
||||
pluralName: "Status Page SCIM Logs",
|
||||
icon: IconProp.Terminal,
|
||||
tableDescription:
|
||||
"Logs of all SCIM provisioning operations for status pages.",
|
||||
})
|
||||
export default class StatusPageSCIMLog extends BaseModel {
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadStatusPageSCIMLog,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
manyToOneRelationColumn: "projectId",
|
||||
type: TableColumnType.Entity,
|
||||
modelType: Project,
|
||||
title: "Project",
|
||||
description: "Relation to Project Resource in which this object belongs",
|
||||
})
|
||||
@ManyToOne(
|
||||
() => {
|
||||
return Project;
|
||||
},
|
||||
{
|
||||
eager: false,
|
||||
nullable: true,
|
||||
onDelete: "CASCADE",
|
||||
orphanedRowAction: "nullify",
|
||||
},
|
||||
)
|
||||
@JoinColumn({ name: "projectId" })
|
||||
public project?: Project = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadStatusPageSCIMLog,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@Index()
|
||||
@TableColumn({
|
||||
type: TableColumnType.ObjectID,
|
||||
required: true,
|
||||
canReadOnRelationQuery: true,
|
||||
title: "Project ID",
|
||||
description: "ID of your OneUptime Project in which this object belongs",
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.ObjectID,
|
||||
nullable: false,
|
||||
transformer: ObjectID.getDatabaseTransformer(),
|
||||
})
|
||||
public projectId?: ObjectID = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadStatusPageSCIMLog,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
manyToOneRelationColumn: "statusPageId",
|
||||
type: TableColumnType.Entity,
|
||||
modelType: StatusPage,
|
||||
title: "Status Page",
|
||||
description: "Relation to Status Page Resource",
|
||||
})
|
||||
@ManyToOne(
|
||||
() => {
|
||||
return StatusPage;
|
||||
},
|
||||
{
|
||||
eager: false,
|
||||
nullable: true,
|
||||
onDelete: "CASCADE",
|
||||
orphanedRowAction: "nullify",
|
||||
},
|
||||
)
|
||||
@JoinColumn({ name: "statusPageId" })
|
||||
public statusPage?: StatusPage = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadStatusPageSCIMLog,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@Index()
|
||||
@TableColumn({
|
||||
type: TableColumnType.ObjectID,
|
||||
required: true,
|
||||
canReadOnRelationQuery: true,
|
||||
title: "Status Page ID",
|
||||
description: "ID of the Status Page",
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.ObjectID,
|
||||
nullable: false,
|
||||
transformer: ObjectID.getDatabaseTransformer(),
|
||||
})
|
||||
public statusPageId?: ObjectID = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadStatusPageSCIMLog,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
manyToOneRelationColumn: "statusPageScimId",
|
||||
type: TableColumnType.Entity,
|
||||
modelType: StatusPageSCIM,
|
||||
title: "Status Page SCIM",
|
||||
description:
|
||||
"Relation to StatusPageSCIM Resource in which this log belongs",
|
||||
})
|
||||
@ManyToOne(
|
||||
() => {
|
||||
return StatusPageSCIM;
|
||||
},
|
||||
{
|
||||
eager: false,
|
||||
nullable: true,
|
||||
onDelete: "CASCADE",
|
||||
orphanedRowAction: "nullify",
|
||||
},
|
||||
)
|
||||
@JoinColumn({ name: "statusPageScimId" })
|
||||
public statusPageScim?: StatusPageSCIM = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadStatusPageSCIMLog,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@Index()
|
||||
@TableColumn({
|
||||
type: TableColumnType.ObjectID,
|
||||
required: true,
|
||||
canReadOnRelationQuery: true,
|
||||
title: "Status Page SCIM ID",
|
||||
description: "ID of your Status Page SCIM configuration",
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.ObjectID,
|
||||
nullable: false,
|
||||
transformer: ObjectID.getDatabaseTransformer(),
|
||||
})
|
||||
public statusPageScimId?: ObjectID = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadStatusPageSCIMLog,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
required: true,
|
||||
type: TableColumnType.ShortText,
|
||||
title: "Operation Type",
|
||||
description:
|
||||
"Type of SCIM operation (e.g., CreateUser, UpdateUser, DeleteUser, ListUsers, GetUser, BulkOperation)",
|
||||
canReadOnRelationQuery: true,
|
||||
})
|
||||
@Column({
|
||||
nullable: false,
|
||||
type: ColumnType.ShortText,
|
||||
length: ColumnLength.ShortText,
|
||||
})
|
||||
public operationType?: string = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadStatusPageSCIMLog,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
required: true,
|
||||
type: TableColumnType.ShortText,
|
||||
title: "Status",
|
||||
description: "Status of the SCIM operation",
|
||||
canReadOnRelationQuery: true,
|
||||
})
|
||||
@Column({
|
||||
nullable: false,
|
||||
type: ColumnType.ShortText,
|
||||
length: ColumnLength.ShortText,
|
||||
})
|
||||
public status?: SCIMLogStatus = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadStatusPageSCIMLog,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
required: false,
|
||||
type: TableColumnType.LongText,
|
||||
title: "Status Message",
|
||||
description: "Short error or status description",
|
||||
canReadOnRelationQuery: false,
|
||||
})
|
||||
@Column({
|
||||
nullable: true,
|
||||
type: ColumnType.LongText,
|
||||
length: ColumnLength.LongText,
|
||||
})
|
||||
public statusMessage?: string = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadStatusPageSCIMLog,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
required: false,
|
||||
type: TableColumnType.VeryLongText,
|
||||
title: "Log Body",
|
||||
description: "Detailed JSON with request/response data",
|
||||
canReadOnRelationQuery: false,
|
||||
})
|
||||
@Column({
|
||||
nullable: true,
|
||||
type: ColumnType.VeryLongText,
|
||||
})
|
||||
public logBody?: string = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadStatusPageSCIMLog,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
required: false,
|
||||
type: TableColumnType.ShortText,
|
||||
title: "HTTP Method",
|
||||
description: "HTTP method used (GET, POST, PUT, PATCH, DELETE)",
|
||||
canReadOnRelationQuery: true,
|
||||
})
|
||||
@Column({
|
||||
nullable: true,
|
||||
type: ColumnType.ShortText,
|
||||
length: ColumnLength.ShortText,
|
||||
})
|
||||
public httpMethod?: string = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadStatusPageSCIMLog,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
required: false,
|
||||
type: TableColumnType.LongText,
|
||||
title: "Request Path",
|
||||
description: "The SCIM endpoint path",
|
||||
canReadOnRelationQuery: false,
|
||||
})
|
||||
@Column({
|
||||
nullable: true,
|
||||
type: ColumnType.LongText,
|
||||
length: ColumnLength.LongText,
|
||||
})
|
||||
public requestPath?: string = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadStatusPageSCIMLog,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
required: false,
|
||||
type: TableColumnType.Number,
|
||||
title: "HTTP Status Code",
|
||||
description: "Response HTTP status code",
|
||||
canReadOnRelationQuery: true,
|
||||
})
|
||||
@Column({
|
||||
nullable: true,
|
||||
type: ColumnType.Number,
|
||||
})
|
||||
public httpStatusCode?: number = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadStatusPageSCIMLog,
|
||||
],
|
||||
update: [],
|
||||
})
|
||||
@Index()
|
||||
@TableColumn({
|
||||
required: false,
|
||||
type: TableColumnType.Email,
|
||||
title: "Affected User Email",
|
||||
description: "Email of the user affected by this operation",
|
||||
canReadOnRelationQuery: true,
|
||||
})
|
||||
@Column({
|
||||
nullable: true,
|
||||
type: ColumnType.Email,
|
||||
length: ColumnLength.Email,
|
||||
})
|
||||
public affectedUserEmail?: string = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
manyToOneRelationColumn: "deletedByUserId",
|
||||
type: TableColumnType.Entity,
|
||||
title: "Deleted by User",
|
||||
modelType: User,
|
||||
description:
|
||||
"Relation to User who deleted this object (if this object was deleted by a User)",
|
||||
})
|
||||
@ManyToOne(
|
||||
() => {
|
||||
return User;
|
||||
},
|
||||
{
|
||||
cascade: false,
|
||||
eager: false,
|
||||
nullable: true,
|
||||
onDelete: "SET NULL",
|
||||
orphanedRowAction: "nullify",
|
||||
},
|
||||
)
|
||||
@JoinColumn({ name: "deletedByUserId" })
|
||||
public deletedByUser?: User = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
type: TableColumnType.ObjectID,
|
||||
title: "Deleted by User ID",
|
||||
description:
|
||||
"User ID who deleted this object (if this object was deleted by a User)",
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.ObjectID,
|
||||
nullable: true,
|
||||
transformer: ObjectID.getDatabaseTransformer(),
|
||||
})
|
||||
public deletedByUserId?: ObjectID = undefined;
|
||||
}
|
||||
@@ -173,6 +173,8 @@ import OnCallDutyPolicyUserOverrideService from "./OnCallDutyPolicyUserOverrideS
|
||||
import MonitorLogService from "./MonitorLogService";
|
||||
|
||||
import OnCallDutyPolicyTimeLogService from "./OnCallDutyPolicyTimeLogService";
|
||||
import ProjectSCIMLogService from "./ProjectSCIMLogService";
|
||||
import StatusPageSCIMLogService from "./StatusPageSCIMLogService";
|
||||
|
||||
const services: Array<BaseService> = [
|
||||
OnCallDutyPolicyTimeLogService,
|
||||
@@ -355,6 +357,9 @@ const services: Array<BaseService> = [
|
||||
WorkspaceSettingService,
|
||||
WorkspaceNotificationRuleService,
|
||||
WorkspaceNotificationLogService,
|
||||
|
||||
ProjectSCIMLogService,
|
||||
StatusPageSCIMLogService,
|
||||
];
|
||||
|
||||
export const AnalyticsServices: Array<
|
||||
|
||||
11
Common/Server/Services/ProjectSCIMLogService.ts
Normal file
11
Common/Server/Services/ProjectSCIMLogService.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import DatabaseService from "./DatabaseService";
|
||||
import Model from "../../Models/DatabaseModels/ProjectSCIMLog";
|
||||
|
||||
export class Service extends DatabaseService<Model> {
|
||||
public constructor() {
|
||||
super(Model);
|
||||
this.hardDeleteItemsOlderThanInDays("createdAt", 3);
|
||||
}
|
||||
}
|
||||
|
||||
export default new Service();
|
||||
11
Common/Server/Services/StatusPageSCIMLogService.ts
Normal file
11
Common/Server/Services/StatusPageSCIMLogService.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import DatabaseService from "./DatabaseService";
|
||||
import Model from "../../Models/DatabaseModels/StatusPageSCIMLog";
|
||||
|
||||
export class Service extends DatabaseService<Model> {
|
||||
public constructor() {
|
||||
super(Model);
|
||||
this.hardDeleteItemsOlderThanInDays("createdAt", 3);
|
||||
}
|
||||
}
|
||||
|
||||
export default new Service();
|
||||
@@ -164,6 +164,9 @@ enum Permission {
|
||||
ReadWorkspaceNotificationLog = "ReadWorkspaceNotificationLog",
|
||||
ReadLlmLog = "ReadLlmLog",
|
||||
|
||||
ReadProjectSCIMLog = "ReadProjectSCIMLog",
|
||||
ReadStatusPageSCIMLog = "ReadStatusPageSCIMLog",
|
||||
|
||||
CreateIncidentOwnerTeam = "CreateIncidentOwnerTeam",
|
||||
DeleteIncidentOwnerTeam = "DeleteIncidentOwnerTeam",
|
||||
EditIncidentOwnerTeam = "EditIncidentOwnerTeam",
|
||||
@@ -1373,6 +1376,23 @@ export class PermissionHelper {
|
||||
isAccessControlPermission: false,
|
||||
},
|
||||
|
||||
{
|
||||
permission: Permission.ReadProjectSCIMLog,
|
||||
title: "Read Project SCIM Log",
|
||||
description:
|
||||
"This permission can read SCIM provisioning logs of the project.",
|
||||
isAssignableToTenant: true,
|
||||
isAccessControlPermission: false,
|
||||
},
|
||||
{
|
||||
permission: Permission.ReadStatusPageSCIMLog,
|
||||
title: "Read Status Page SCIM Log",
|
||||
description:
|
||||
"This permission can read SCIM provisioning logs of status pages in the project.",
|
||||
isAssignableToTenant: true,
|
||||
isAccessControlPermission: false,
|
||||
},
|
||||
|
||||
{
|
||||
permission: Permission.CreateProjectMonitorStatus,
|
||||
title: "Create Monitor Status",
|
||||
|
||||
7
Common/Types/SCIM/SCIMLogStatus.ts
Normal file
7
Common/Types/SCIM/SCIMLogStatus.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
enum SCIMLogStatus {
|
||||
Success = "Success",
|
||||
Error = "Error",
|
||||
Warning = "Warning",
|
||||
}
|
||||
|
||||
export default SCIMLogStatus;
|
||||
217
Dashboard/src/Components/SCIMLogs/ProjectSCIMLogsTable.tsx
Normal file
217
Dashboard/src/Components/SCIMLogs/ProjectSCIMLogsTable.tsx
Normal file
@@ -0,0 +1,217 @@
|
||||
import React, { FunctionComponent, ReactElement, useState } from "react";
|
||||
import ModelTable from "Common/UI/Components/ModelTable/ModelTable";
|
||||
import ProjectSCIMLog from "Common/Models/DatabaseModels/ProjectSCIMLog";
|
||||
import FieldType from "Common/UI/Components/Types/FieldType";
|
||||
import Columns from "Common/UI/Components/ModelTable/Columns";
|
||||
import Pill from "Common/UI/Components/Pill/Pill";
|
||||
import { Green, Red, Yellow } from "Common/Types/BrandColors";
|
||||
import Color from "Common/Types/Color";
|
||||
import SCIMLogStatus from "Common/Types/SCIM/SCIMLogStatus";
|
||||
import ProjectUtil from "Common/UI/Utils/Project";
|
||||
import Filter from "Common/UI/Components/ModelFilter/Filter";
|
||||
import ConfirmModal from "Common/UI/Components/Modal/ConfirmModal";
|
||||
import IconProp from "Common/Types/Icon/IconProp";
|
||||
import { ButtonStyleType } from "Common/UI/Components/Button/Button";
|
||||
import Query from "Common/Types/BaseDatabase/Query";
|
||||
import BaseModel from "Common/Types/Workflow/Components/BaseModel";
|
||||
|
||||
export interface ProjectSCIMLogsTableProps {
|
||||
query?: Query<BaseModel>;
|
||||
}
|
||||
|
||||
const ProjectSCIMLogsTable: FunctionComponent<ProjectSCIMLogsTableProps> = (
|
||||
props: ProjectSCIMLogsTableProps,
|
||||
): ReactElement => {
|
||||
const [showModal, setShowModal] = useState<boolean>(false);
|
||||
const [modalText, setModalText] = useState<string>("");
|
||||
const [modalTitle, setModalTitle] = useState<string>("");
|
||||
|
||||
const getStatusColor = (status: SCIMLogStatus): Color => {
|
||||
switch (status) {
|
||||
case SCIMLogStatus.Success:
|
||||
return Green;
|
||||
case SCIMLogStatus.Warning:
|
||||
return Yellow;
|
||||
case SCIMLogStatus.Error:
|
||||
return Red;
|
||||
default:
|
||||
return Green;
|
||||
}
|
||||
};
|
||||
|
||||
const defaultColumns: Columns<ProjectSCIMLog> = [
|
||||
{
|
||||
field: { operationType: true },
|
||||
title: "Operation",
|
||||
type: FieldType.Text,
|
||||
noValueMessage: "-",
|
||||
},
|
||||
{
|
||||
field: { affectedUserEmail: true },
|
||||
title: "User Email",
|
||||
type: FieldType.Email,
|
||||
hideOnMobile: true,
|
||||
noValueMessage: "-",
|
||||
},
|
||||
{
|
||||
field: { affectedGroupName: true },
|
||||
title: "Group Name",
|
||||
type: FieldType.Text,
|
||||
hideOnMobile: true,
|
||||
noValueMessage: "-",
|
||||
},
|
||||
{
|
||||
field: { httpMethod: true },
|
||||
title: "Method",
|
||||
type: FieldType.Text,
|
||||
hideOnMobile: true,
|
||||
noValueMessage: "-",
|
||||
},
|
||||
{
|
||||
field: { httpStatusCode: true },
|
||||
title: "Status Code",
|
||||
type: FieldType.Number,
|
||||
hideOnMobile: true,
|
||||
noValueMessage: "-",
|
||||
},
|
||||
{
|
||||
field: { createdAt: true },
|
||||
title: "Time",
|
||||
type: FieldType.DateTime,
|
||||
},
|
||||
{
|
||||
field: { status: true },
|
||||
title: "Status",
|
||||
type: FieldType.Text,
|
||||
getElement: (item: ProjectSCIMLog): ReactElement => {
|
||||
if (item["status"]) {
|
||||
return (
|
||||
<Pill
|
||||
isMinimal={false}
|
||||
color={getStatusColor(item["status"] as SCIMLogStatus)}
|
||||
text={item["status"] as string}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return <></>;
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const defaultFilters: Array<Filter<ProjectSCIMLog>> = [
|
||||
{ field: { createdAt: true }, title: "Time", type: FieldType.Date },
|
||||
{ field: { status: true }, title: "Status", type: FieldType.Dropdown },
|
||||
{
|
||||
field: { operationType: true },
|
||||
title: "Operation Type",
|
||||
type: FieldType.Dropdown,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<ModelTable<ProjectSCIMLog>
|
||||
modelType={ProjectSCIMLog}
|
||||
id="project-scim-logs-table"
|
||||
name="SCIM Logs"
|
||||
isDeleteable={false}
|
||||
isEditable={false}
|
||||
isCreateable={false}
|
||||
showViewIdButton={true}
|
||||
isViewable={false}
|
||||
userPreferencesKey="project-scim-logs-table"
|
||||
query={{
|
||||
projectId: ProjectUtil.getCurrentProjectId()!,
|
||||
...(props.query || {}),
|
||||
}}
|
||||
selectMoreFields={{
|
||||
logBody: true,
|
||||
statusMessage: true,
|
||||
requestPath: true,
|
||||
}}
|
||||
cardProps={{
|
||||
title: "SCIM Logs",
|
||||
description: "Logs of all SCIM provisioning operations.",
|
||||
}}
|
||||
noItemsMessage="No SCIM logs yet."
|
||||
showRefreshButton={true}
|
||||
columns={defaultColumns}
|
||||
filters={defaultFilters}
|
||||
actionButtons={[
|
||||
{
|
||||
title: "View Details",
|
||||
buttonStyleType: ButtonStyleType.NORMAL,
|
||||
icon: IconProp.Info,
|
||||
onClick: async (
|
||||
item: ProjectSCIMLog,
|
||||
onCompleteAction: VoidFunction,
|
||||
) => {
|
||||
let logDetails: string = "";
|
||||
if (item["logBody"]) {
|
||||
try {
|
||||
const parsed: object = JSON.parse(item["logBody"] as string);
|
||||
logDetails = JSON.stringify(parsed, null, 2);
|
||||
} catch {
|
||||
logDetails = item["logBody"] as string;
|
||||
}
|
||||
}
|
||||
if (item["statusMessage"]) {
|
||||
logDetails =
|
||||
`Status Message: ${item["statusMessage"]}\n\n` + logDetails;
|
||||
}
|
||||
if (item["requestPath"]) {
|
||||
logDetails =
|
||||
`Request Path: ${item["requestPath"]}\n\n` + logDetails;
|
||||
}
|
||||
setModalText(logDetails || "No details available");
|
||||
setModalTitle("SCIM Operation Details");
|
||||
setShowModal(true);
|
||||
onCompleteAction();
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "View Status Message",
|
||||
buttonStyleType: ButtonStyleType.NORMAL,
|
||||
icon: IconProp.Error,
|
||||
onClick: async (
|
||||
item: ProjectSCIMLog,
|
||||
onCompleteAction: VoidFunction,
|
||||
) => {
|
||||
setModalText(
|
||||
(item["statusMessage"] as string) || "No status message",
|
||||
);
|
||||
setModalTitle("Status Message");
|
||||
setShowModal(true);
|
||||
onCompleteAction();
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
{showModal && (
|
||||
<ConfirmModal
|
||||
title={modalTitle}
|
||||
description={
|
||||
<pre
|
||||
style={{
|
||||
whiteSpace: "pre-wrap",
|
||||
wordBreak: "break-word",
|
||||
maxHeight: "400px",
|
||||
overflow: "auto",
|
||||
}}
|
||||
>
|
||||
{modalText}
|
||||
</pre>
|
||||
}
|
||||
onSubmit={() => {
|
||||
setShowModal(false);
|
||||
}}
|
||||
submitButtonText="Close"
|
||||
submitButtonType={ButtonStyleType.NORMAL}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProjectSCIMLogsTable;
|
||||
210
Dashboard/src/Components/SCIMLogs/StatusPageSCIMLogsTable.tsx
Normal file
210
Dashboard/src/Components/SCIMLogs/StatusPageSCIMLogsTable.tsx
Normal file
@@ -0,0 +1,210 @@
|
||||
import React, { FunctionComponent, ReactElement, useState } from "react";
|
||||
import ModelTable from "Common/UI/Components/ModelTable/ModelTable";
|
||||
import StatusPageSCIMLog from "Common/Models/DatabaseModels/StatusPageSCIMLog";
|
||||
import FieldType from "Common/UI/Components/Types/FieldType";
|
||||
import Columns from "Common/UI/Components/ModelTable/Columns";
|
||||
import Pill from "Common/UI/Components/Pill/Pill";
|
||||
import { Green, Red, Yellow } from "Common/Types/BrandColors";
|
||||
import Color from "Common/Types/Color";
|
||||
import SCIMLogStatus from "Common/Types/SCIM/SCIMLogStatus";
|
||||
import ProjectUtil from "Common/UI/Utils/Project";
|
||||
import Filter from "Common/UI/Components/ModelFilter/Filter";
|
||||
import ConfirmModal from "Common/UI/Components/Modal/ConfirmModal";
|
||||
import IconProp from "Common/Types/Icon/IconProp";
|
||||
import { ButtonStyleType } from "Common/UI/Components/Button/Button";
|
||||
import Query from "Common/Types/BaseDatabase/Query";
|
||||
import BaseModel from "Common/Types/Workflow/Components/BaseModel";
|
||||
|
||||
export interface StatusPageSCIMLogsTableProps {
|
||||
query?: Query<BaseModel>;
|
||||
}
|
||||
|
||||
const StatusPageSCIMLogsTable: FunctionComponent<
|
||||
StatusPageSCIMLogsTableProps
|
||||
> = (props: StatusPageSCIMLogsTableProps): ReactElement => {
|
||||
const [showModal, setShowModal] = useState<boolean>(false);
|
||||
const [modalText, setModalText] = useState<string>("");
|
||||
const [modalTitle, setModalTitle] = useState<string>("");
|
||||
|
||||
const getStatusColor = (status: SCIMLogStatus): Color => {
|
||||
switch (status) {
|
||||
case SCIMLogStatus.Success:
|
||||
return Green;
|
||||
case SCIMLogStatus.Warning:
|
||||
return Yellow;
|
||||
case SCIMLogStatus.Error:
|
||||
return Red;
|
||||
default:
|
||||
return Green;
|
||||
}
|
||||
};
|
||||
|
||||
const defaultColumns: Columns<StatusPageSCIMLog> = [
|
||||
{
|
||||
field: { operationType: true },
|
||||
title: "Operation",
|
||||
type: FieldType.Text,
|
||||
noValueMessage: "-",
|
||||
},
|
||||
{
|
||||
field: { affectedUserEmail: true },
|
||||
title: "User Email",
|
||||
type: FieldType.Email,
|
||||
hideOnMobile: true,
|
||||
noValueMessage: "-",
|
||||
},
|
||||
{
|
||||
field: { httpMethod: true },
|
||||
title: "Method",
|
||||
type: FieldType.Text,
|
||||
hideOnMobile: true,
|
||||
noValueMessage: "-",
|
||||
},
|
||||
{
|
||||
field: { httpStatusCode: true },
|
||||
title: "Status Code",
|
||||
type: FieldType.Number,
|
||||
hideOnMobile: true,
|
||||
noValueMessage: "-",
|
||||
},
|
||||
{
|
||||
field: { createdAt: true },
|
||||
title: "Time",
|
||||
type: FieldType.DateTime,
|
||||
},
|
||||
{
|
||||
field: { status: true },
|
||||
title: "Status",
|
||||
type: FieldType.Text,
|
||||
getElement: (item: StatusPageSCIMLog): ReactElement => {
|
||||
if (item["status"]) {
|
||||
return (
|
||||
<Pill
|
||||
isMinimal={false}
|
||||
color={getStatusColor(item["status"] as SCIMLogStatus)}
|
||||
text={item["status"] as string}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return <></>;
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const defaultFilters: Array<Filter<StatusPageSCIMLog>> = [
|
||||
{ field: { createdAt: true }, title: "Time", type: FieldType.Date },
|
||||
{ field: { status: true }, title: "Status", type: FieldType.Dropdown },
|
||||
{
|
||||
field: { operationType: true },
|
||||
title: "Operation Type",
|
||||
type: FieldType.Dropdown,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<ModelTable<StatusPageSCIMLog>
|
||||
modelType={StatusPageSCIMLog}
|
||||
id="status-page-scim-logs-table"
|
||||
name="SCIM Logs"
|
||||
isDeleteable={false}
|
||||
isEditable={false}
|
||||
isCreateable={false}
|
||||
showViewIdButton={true}
|
||||
isViewable={false}
|
||||
userPreferencesKey="status-page-scim-logs-table"
|
||||
query={{
|
||||
projectId: ProjectUtil.getCurrentProjectId()!,
|
||||
...(props.query || {}),
|
||||
}}
|
||||
selectMoreFields={{
|
||||
logBody: true,
|
||||
statusMessage: true,
|
||||
requestPath: true,
|
||||
}}
|
||||
cardProps={{
|
||||
title: "SCIM Logs",
|
||||
description: "Logs of all SCIM provisioning operations for this status page.",
|
||||
}}
|
||||
noItemsMessage="No SCIM logs yet."
|
||||
showRefreshButton={true}
|
||||
columns={defaultColumns}
|
||||
filters={defaultFilters}
|
||||
actionButtons={[
|
||||
{
|
||||
title: "View Details",
|
||||
buttonStyleType: ButtonStyleType.NORMAL,
|
||||
icon: IconProp.Info,
|
||||
onClick: async (
|
||||
item: StatusPageSCIMLog,
|
||||
onCompleteAction: VoidFunction,
|
||||
) => {
|
||||
let logDetails: string = "";
|
||||
if (item["logBody"]) {
|
||||
try {
|
||||
const parsed: object = JSON.parse(item["logBody"] as string);
|
||||
logDetails = JSON.stringify(parsed, null, 2);
|
||||
} catch {
|
||||
logDetails = item["logBody"] as string;
|
||||
}
|
||||
}
|
||||
if (item["statusMessage"]) {
|
||||
logDetails =
|
||||
`Status Message: ${item["statusMessage"]}\n\n` + logDetails;
|
||||
}
|
||||
if (item["requestPath"]) {
|
||||
logDetails =
|
||||
`Request Path: ${item["requestPath"]}\n\n` + logDetails;
|
||||
}
|
||||
setModalText(logDetails || "No details available");
|
||||
setModalTitle("SCIM Operation Details");
|
||||
setShowModal(true);
|
||||
onCompleteAction();
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "View Status Message",
|
||||
buttonStyleType: ButtonStyleType.NORMAL,
|
||||
icon: IconProp.Error,
|
||||
onClick: async (
|
||||
item: StatusPageSCIMLog,
|
||||
onCompleteAction: VoidFunction,
|
||||
) => {
|
||||
setModalText(
|
||||
(item["statusMessage"] as string) || "No status message",
|
||||
);
|
||||
setModalTitle("Status Message");
|
||||
setShowModal(true);
|
||||
onCompleteAction();
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
{showModal && (
|
||||
<ConfirmModal
|
||||
title={modalTitle}
|
||||
description={
|
||||
<pre
|
||||
style={{
|
||||
whiteSpace: "pre-wrap",
|
||||
wordBreak: "break-word",
|
||||
maxHeight: "400px",
|
||||
overflow: "auto",
|
||||
}}
|
||||
>
|
||||
{modalText}
|
||||
</pre>
|
||||
}
|
||||
onSubmit={() => {
|
||||
setShowModal(false);
|
||||
}}
|
||||
submitButtonText="Close"
|
||||
submitButtonType={ButtonStyleType.NORMAL}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default StatusPageSCIMLogsTable;
|
||||
@@ -22,6 +22,8 @@ import React, {
|
||||
} from "react";
|
||||
import IconProp from "Common/Types/Icon/IconProp";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import Tabs from "Common/UI/Components/Tabs/Tabs";
|
||||
import ProjectSCIMLogsTable from "../../Components/SCIMLogs/ProjectSCIMLogsTable";
|
||||
|
||||
const SCIMPage: FunctionComponent<PageComponentProps> = (
|
||||
_props: PageComponentProps,
|
||||
@@ -65,8 +67,12 @@ const SCIMPage: FunctionComponent<PageComponentProps> = (
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<>
|
||||
<ModelTable<ProjectSCIM>
|
||||
<Tabs
|
||||
tabs={[
|
||||
{
|
||||
name: "Configuration",
|
||||
children: (
|
||||
<ModelTable<ProjectSCIM>
|
||||
key={refresher.toString()}
|
||||
modelType={ProjectSCIM}
|
||||
userPreferencesKey={"project-scim-table"}
|
||||
@@ -262,9 +268,18 @@ const SCIMPage: FunctionComponent<PageComponentProps> = (
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Logs",
|
||||
children: <ProjectSCIMLogsTable />,
|
||||
},
|
||||
]}
|
||||
onTabChange={() => {}}
|
||||
/>
|
||||
|
||||
{showSCIMUrlId && currentSCIMConfig && (
|
||||
{showSCIMUrlId && currentSCIMConfig && (
|
||||
<ConfirmModal
|
||||
title={`SCIM Configuration URLs`}
|
||||
description={
|
||||
@@ -403,7 +418,6 @@ const SCIMPage: FunctionComponent<PageComponentProps> = (
|
||||
submitButtonType={ButtonStyleType.NORMAL}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -20,6 +20,8 @@ import React, {
|
||||
} from "react";
|
||||
import IconProp from "Common/Types/Icon/IconProp";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import Tabs from "Common/UI/Components/Tabs/Tabs";
|
||||
import StatusPageSCIMLogsTable from "../../../Components/SCIMLogs/StatusPageSCIMLogsTable";
|
||||
|
||||
const SCIMPage: FunctionComponent<PageComponentProps> = (
|
||||
_props: PageComponentProps,
|
||||
@@ -64,8 +66,12 @@ const SCIMPage: FunctionComponent<PageComponentProps> = (
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<>
|
||||
<ModelTable<StatusPageSCIM>
|
||||
<Tabs
|
||||
tabs={[
|
||||
{
|
||||
name: "Configuration",
|
||||
children: (
|
||||
<ModelTable<StatusPageSCIM>
|
||||
key={refresher.toString()}
|
||||
modelType={StatusPageSCIM}
|
||||
userPreferencesKey={"status-page-scim-table"}
|
||||
@@ -216,9 +222,22 @@ const SCIMPage: FunctionComponent<PageComponentProps> = (
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Logs",
|
||||
children: (
|
||||
<StatusPageSCIMLogsTable
|
||||
query={{ statusPageId: modelId }}
|
||||
/>
|
||||
),
|
||||
},
|
||||
]}
|
||||
onTabChange={() => {}}
|
||||
/>
|
||||
|
||||
{showSCIMUrlId && currentSCIMConfig ? (
|
||||
{showSCIMUrlId && currentSCIMConfig ? (
|
||||
<ConfirmModal
|
||||
title={`SCIM URLs - ${currentSCIMConfig.name}`}
|
||||
description={
|
||||
@@ -346,7 +365,6 @@ const SCIMPage: FunctionComponent<PageComponentProps> = (
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user