diff --git a/docs/widgets/services/gitlab.md b/docs/widgets/services/gitlab.md
new file mode 100644
index 00000000..32712963
--- /dev/null
+++ b/docs/widgets/services/gitlab.md
@@ -0,0 +1,20 @@
+---
+title: Gitlab
+description: Gitlab Widget Configuration
+---
+
+Learn more about [Gitlab](https://gitlab.com).
+
+API requires a personal access token with either `read_api` or `api` permission. See the [gitlab documentation](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html#create-a-personal-access-token) for details on generating one.
+
+Allowed fields: `["events", "issues", "openIssues", "closedIssues", "mergeRequests", "openMergeRequests",
+"closedMergeRequests"]`.
+
+```yaml
+widget:
+ type: gitlab
+ url: http://gitlab.host.or.ip:port
+ key: personal-access-token
+ issueState: all # supports "opened", "closed" and defaults to "all"
+ mergeRequestState: all # supports "opened", "closed", "locked" and defaults to "all"
+```
diff --git a/docs/widgets/services/index.md b/docs/widgets/services/index.md
index 8ea2e933..1de62be4 100644
--- a/docs/widgets/services/index.md
+++ b/docs/widgets/services/index.md
@@ -40,6 +40,7 @@ You can also find a list of all available service widgets in the sidebar navigat
- [Gatus](gatus.md)
- [Ghostfolio](ghostfolio.md)
- [Gitea](gitea.md)
+- [Gitlab](gitlab.md)
- [Glances](glances.md)
- [Gluetun](gluetun.md)
- [Gotify](gotify.md)
diff --git a/mkdocs.yml b/mkdocs.yml
index 42abce30..58e54f49 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -63,6 +63,7 @@ nav:
- widgets/services/gatus.md
- widgets/services/ghostfolio.md
- widgets/services/gitea.md
+ - widgets/services/gitlab.md
- widgets/services/glances.md
- widgets/services/gluetun.md
- widgets/services/gotify.md
diff --git a/public/locales/en/common.json b/public/locales/en/common.json
index 81576984..d219bcc3 100644
--- a/public/locales/en/common.json
+++ b/public/locales/en/common.json
@@ -988,5 +988,14 @@
"memory": "MEM",
"disk": "Disk",
"network": "NET"
+ },
+ "gitlab": {
+ "events": "Events",
+ "issues": "Issues",
+ "issuesOpen": "Open Issues",
+ "issuesClosed": "Closed Issues",
+ "merges": "Merge Requests",
+ "mergesOpen": "Open Merge Requests",
+ "mergesClosed": "Closed Merge Requests"
}
}
diff --git a/src/utils/proxy/handlers/credentialed.js b/src/utils/proxy/handlers/credentialed.js
index eb2aab69..19185063 100644
--- a/src/utils/proxy/handlers/credentialed.js
+++ b/src/utils/proxy/handlers/credentialed.js
@@ -93,6 +93,8 @@ export default async function credentialedProxyHandler(req, res, map) {
}
} else if (widget.type === "wgeasy") {
headers.Authorization = widget.password;
+ } else if (widget.type === "gitlab") {
+ headers["PRIVATE-TOKEN"] = widget.key
} else {
headers["X-API-Key"] = `${widget.key}`;
}
diff --git a/src/widgets/components.js b/src/widgets/components.js
index 3cba84d2..4daa0c83 100644
--- a/src/widgets/components.js
+++ b/src/widgets/components.js
@@ -37,6 +37,7 @@ const components = {
gatus: dynamic(() => import("./gatus/component")),
ghostfolio: dynamic(() => import("./ghostfolio/component")),
gitea: dynamic(() => import("./gitea/component")),
+ gitlab: dynamic(() => import("./gitlab/component")),
glances: dynamic(() => import("./glances/component")),
gluetun: dynamic(() => import("./gluetun/component")),
gotify: dynamic(() => import("./gotify/component")),
diff --git a/src/widgets/gitlab/component.jsx b/src/widgets/gitlab/component.jsx
new file mode 100644
index 00000000..b6d3004d
--- /dev/null
+++ b/src/widgets/gitlab/component.jsx
@@ -0,0 +1,51 @@
+import Container from "components/services/widget/container";
+import Block from "components/services/widget/block";
+import useWidgetAPI from "utils/proxy/use-widget-api";
+
+export default function Component({ service }) {
+ const { widget } = service;
+
+ const { data: gitlabEvents, error: gitlabEventsError } = useWidgetAPI(widget, "events");
+
+ if (gitlabEventsError) {
+ return ;
+ }
+
+ if (!gitlabEvents) {
+ return (
+
+
+
+
+
+
+
+
+
+ );
+ }
+
+ const issues = {
+ open: gitlabEvents.issues.filter(event => event.action_name.toLowerCase() === "opened").length,
+ closed: gitlabEvents.issues.filter(event => event.action_name.toLowerCase() === "closed").length,
+ count: gitlabEvents.issues.length
+ };
+
+ const merges = {
+ open: gitlabEvents.merges.filter(event => event.action_name.toLowerCase() === "opened").length,
+ closed: gitlabEvents.merges.filter(event => event.action_name.toLowerCase() === "closed").length,
+ count: gitlabEvents.merges.length
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/widgets/gitlab/widget.js b/src/widgets/gitlab/widget.js
new file mode 100644
index 00000000..90d7336b
--- /dev/null
+++ b/src/widgets/gitlab/widget.js
@@ -0,0 +1,39 @@
+import { asJson } from "utils/proxy/api-helpers";
+import credentialedProxyHandler from "utils/proxy/handlers/credentialed";
+
+const widget = {
+ api: "{url}/api/v1/{endpoint}",
+ proxyHandler: credentialedProxyHandler,
+ mappings: {
+ events: {
+ endpoint: "events",
+ map: (data) => ({
+ merges: asJson(data).filter((event) => event.target_type?.toLowerCase() === "merge_request"),
+ issues: asJson(data).filter((event) => event.target_type?.toLowerCase() === "issue"),
+ events: asJson(data).length
+ })
+ },
+ issues: {
+ endpoint: "issues",
+ params: ["state"]
+ },
+ openIssues: {
+ endpoint: "issues?state=opened"
+ },
+ closedIssues: {
+ endpoint: "issues?state=closed"
+ },
+ mergeRequests: {
+ endpoint: "merge_requests",
+ params: ["state"]
+ },
+ openMergeRequests: {
+ endpoint: "merge_requests?state=opened"
+ },
+ closedMergeRequests: {
+ endpoint: "merge_requests?state=closed"
+ }
+ }
+};
+
+export default widget;
diff --git a/src/widgets/widgets.js b/src/widgets/widgets.js
index 79110378..412f4cda 100644
--- a/src/widgets/widgets.js
+++ b/src/widgets/widgets.js
@@ -31,6 +31,7 @@ import gamedig from "./gamedig/widget";
import gatus from "./gatus/widget";
import ghostfolio from "./ghostfolio/widget";
import gitea from "./gitea/widget";
+import gitlab from "./gitlab/widget";
import glances from "./glances/widget";
import gluetun from "./gluetun/widget";
import gotify from "./gotify/widget";
@@ -161,6 +162,7 @@ const widgets = {
gatus,
ghostfolio,
gitea,
+ gitlab,
glances,
gluetun,
gotify,