diff --git a/lib/towerops_web/controllers/graphql_docs_controller.ex b/lib/towerops_web/controllers/graphql_docs_controller.ex new file mode 100644 index 00000000..b5f18ca9 --- /dev/null +++ b/lib/towerops_web/controllers/graphql_docs_controller.ex @@ -0,0 +1,26 @@ +defmodule ToweropsWeb.GraphQLDocsController do + @moduledoc "Controller for GraphQL API documentation page." + use ToweropsWeb, :controller + + alias Towerops.ApiTokens + + def index(conn, _params) do + sample_token = + case conn.assigns[:current_scope] do + %{user: user, organization: org} when not is_nil(user) and not is_nil(org) -> + tokens = ApiTokens.list_organization_api_tokens(org.id) + + if Enum.empty?(tokens) do + "your-api-token-here (create one in your organization settings)" + else + token = List.first(tokens) + "your-api-token-here (use: #{token.name})" + end + + _ -> + "YOUR_API_TOKEN" + end + + render(conn, :index, sample_token: sample_token) + end +end diff --git a/lib/towerops_web/controllers/graphql_docs_html.ex b/lib/towerops_web/controllers/graphql_docs_html.ex new file mode 100644 index 00000000..53f3a3f3 --- /dev/null +++ b/lib/towerops_web/controllers/graphql_docs_html.ex @@ -0,0 +1,6 @@ +defmodule ToweropsWeb.GraphQLDocsHTML do + @moduledoc "GraphQL API documentation pages." + use ToweropsWeb, :html + + embed_templates "graphql_docs_html/*" +end diff --git a/lib/towerops_web/controllers/graphql_docs_html/index.html.heex b/lib/towerops_web/controllers/graphql_docs_html/index.html.heex new file mode 100644 index 00000000..7f191eac --- /dev/null +++ b/lib/towerops_web/controllers/graphql_docs_html/index.html.heex @@ -0,0 +1,737 @@ + +
+ The TowerOps GraphQL API provides a flexible, strongly-typed interface for querying and managing your network infrastructure. + Request exactly the data you need in a single request — no over-fetching, no under-fetching. +
++ The GraphQL API uses the same authentication as the + <.link navigate={~p"/docs/api"} class="text-blue-600 hover:text-blue-500 dark:text-blue-400">REST API + and provides equivalent functionality with the added benefit of nested queries and precise field selection. +
+
+ Authenticate using a Bearer token in the Authorization header.
+ API tokens can be created in your organization settings.
+
Authorization: Bearer <%= @sample_token %>
+
+ All GraphQL requests are sent as POST to a single endpoint:
+
POST https://app.towerops.net/api/graphql
+
+ Send a JSON body with a query field
+ and optional variables:
+
curl -X POST https://app.towerops.net/api/graphql \
+ -H "Authorization: Bearer <%= @sample_token %>" \
+ -H "Content-Type: application/json" \
+ -d '{"query": "{ devices { id name ipAddress status } }"}'
+ List all devices. Optionally filter by site or status.
+| Argument | +Type | +Description | +
|---|---|---|
| siteId | ID | Filter by site |
| status | String | Filter by status (up, down, unknown) |
| limit | Int | Max results (default: 100) |
{
+ devices(siteId: "uuid", status: "up") {
+ id
+ name
+ ipAddress
+ status
+ lastCheckedAt
+ monitoringEnabled
+ snmpEnabled
+ deviceRole
+ site {
+ id
+ name
+ location
+ }
+ }
+}
+ Get a single device by ID.
+{
+ device(id: "device-uuid") {
+ id
+ name
+ ipAddress
+ status
+ description
+ snmpVersion
+ snmpPort
+ checkIntervalSeconds
+ site { id name }
+ }
+}
+ List all sites for the organization.
+{
+ sites {
+ id
+ name
+ description
+ location
+ snmpVersion
+ snmpCommunity
+ insertedAt
+ }
+}
+ Get a single site by ID.
+{ site(id: "site-uuid") { id name location } }
+ List alerts. Filter by status or device.
+| Argument | +Type | +Description | +
|---|---|---|
| status | String | active, acknowledged, or resolved |
| deviceId | ID | Filter by device |
| limit | Int | Max results (default: 100) |
{
+ alerts(status: "active", limit: 10) {
+ id
+ alertType
+ message
+ triggeredAt
+ acknowledgedAt
+ resolvedAt
+ deviceId
+ device {
+ id
+ name
+ ipAddress
+ }
+ acknowledgedByEmail
+ }
+}
+ {
+ agents {
+ id
+ name
+ enabled
+ lastSeenAt
+ lastIp
+ isCloudPoller
+ deviceCount
+ metadata
+ }
+}
+
+# Single agent
+{ agent(id: "agent-uuid") { id name enabled deviceCount } }
+ {
+ organization {
+ id
+ name
+ slug
+ subscriptionPlan
+ useSites
+ snmpVersion
+ snmpCommunity
+ snmpPort
+ snmpTransport
+ mikrotikEnabled
+ mikrotikUsername
+ mikrotikPort
+ mikrotikUseSsl
+ defaultAgentTokenId
+ }
+}
+ {
+ members {
+ id
+ email
+ role
+ insertedAt
+ }
+}
+ {
+ integrations {
+ id
+ provider
+ enabled
+ syncIntervalMinutes
+ lastSyncedAt
+ lastSyncStatus
+ }
+}
+ {
+ activity(limit: 20) {
+ summary
+ detail
+ timestamp
+ severity
+ deviceName
+ siteName
+ link
+ type
+ }
+}
+ Get time-series metrics for a device.
+| Argument | +Type | +Description | +
|---|---|---|
| deviceId | ID! | Required device ID |
| sensorType | String | Filter by sensor type |
| timeRange | String | e.g. "24h", "72h" (default: "24h") |
{
+ deviceMetrics(deviceId: "device-uuid", timeRange: "48h") {
+ timestamp
+ value
+ status
+ checkName
+ checkType
+ }
+}
+ Get SNMP interfaces for a device.
+{
+ deviceInterfaces(deviceId: "device-uuid") {
+ id
+ ifIndex
+ ifName
+ ifDescr
+ ifType
+ ifSpeed
+ ifAdminStatus
+ ifOperStatus
+ ifAlias
+ }
+}
+ mutation {
+ createDevice(input: {
+ ipAddress: "192.168.1.1"
+ name: "Core Router"
+ siteId: "site-uuid"
+ snmpEnabled: true
+ snmpVersion: "2c"
+ snmpCommunity: "public"
+ }) {
+ id
+ name
+ ipAddress
+ status
+ }
+}
+ mutation {
+ updateDevice(id: "device-uuid", input: {
+ name: "Updated Router"
+ monitoringEnabled: false
+ }) {
+ id
+ name
+ monitoringEnabled
+ }
+}
+ mutation {
+ deleteDevice(id: "device-uuid") {
+ success
+ message
+ }
+}
+ mutation {
+ createSite(input: {
+ name: "Main Office"
+ location: "New York, NY"
+ snmpCommunity: "public"
+ }) {
+ id
+ name
+ location
+ }
+}
+ Same pattern as device mutations.
+mutation {
+ acknowledgeAlert(id: "alert-uuid") {
+ id
+ acknowledgedAt
+ acknowledgedByEmail
+ }
+}
+ mutation {
+ resolveAlert(id: "alert-uuid") {
+ id
+ resolvedAt
+ }
+}
+ Creates a new agent token. The raw token is only returned once.
+mutation {
+ createAgent(name: "DC1 Agent") {
+ id
+ name
+ token
+ }
+}
+ mutation { deleteAgent(id: "agent-uuid") { success message } }
+ mutation {
+ updateOrganization(input: {
+ name: "My Network"
+ useSites: true
+ snmpVersion: "2c"
+ snmpCommunity: "public"
+ mikrotikEnabled: true
+ mikrotikUsername: "admin"
+ mikrotikPort: 8729
+ mikrotikUseSsl: true
+ }) {
+ id
+ name
+ useSites
+ snmpVersion
+ mikrotikEnabled
+ }
+}
+ mutation {
+ sendInvitation(email: "user@example.com", role: "admin") {
+ id
+ email
+ role
+ expiresAt
+ }
+}
+ mutation {
+ updateMemberRole(id: "user-uuid", role: "admin") {
+ id
+ email
+ role
+ }
+}
+ mutation { removeMember(id: "user-uuid") { success message } }
+mutation { cancelInvitation(id: "invitation-uuid") { success message } }
+ mutation {
+ createIntegration(input: {
+ provider: "preseem"
+ enabled: true
+ credentials: "{\"api_key\": \"xxx\"}"
+ syncIntervalMinutes: 10
+ }) {
+ id
+ provider
+ enabled
+ }
+}
+ mutation {
+ testIntegration(id: "integration-uuid") {
+ success
+ message
+ }
+}
+ + All field names use camelCase in GraphQL queries (Absinthe automatically converts from Elixir's snake_case). +
+ ++ id, name, ipAddress, description, displayOrder, status, lastCheckedAt, lastStatusChangeAt, + monitoringEnabled, checkIntervalSeconds, snmpEnabled, snmpVersion, snmpCommunity, snmpCommunitySource, + snmpPort, snmpTransport, snmpTransportSource, lastDiscoveryAt, lastSnmpPollAt, + snmpv3SecurityLevel, snmpv3Username, snmpv3AuthProtocol, snmpv3PrivProtocol, snmpv3CredentialSource, + mikrotikUsername, mikrotikPort, mikrotikSshPort, mikrotikUseSsl, mikrotikEnabled, mikrotikCredentialSource, + deviceRole, deviceRoleSource, siteId, organizationId, insertedAt, updatedAt, site +
+ ++ id, name, description, location, displayOrder, snmpVersion, snmpCommunity, snmpPort, snmpTransport, + snmpv3SecurityLevel, snmpv3Username, snmpv3AuthProtocol, snmpv3PrivProtocol, + mikrotikUsername, mikrotikPort, mikrotikSshPort, mikrotikUseSsl, mikrotikEnabled, + organizationId, agentTokenId, parentSiteId, insertedAt, updatedAt +
+ ++ id, alertType, message, triggeredAt, acknowledgedAt, resolvedAt, emailSentAt, + deviceId, acknowledgedById, gaiiaImpact, insertedAt, device, acknowledgedByEmail +
+ ++ id, name, enabled, isCloudPoller, allowRemoteDebug, lastSeenAt, lastIp, metadata, + organizationId, insertedAt, updatedAt, deviceCount +
+ ++ id, name, slug, subscriptionPlan, useSites, snmpVersion, snmpCommunity, snmpPort, snmpTransport, + snmpv3SecurityLevel, snmpv3Username, snmpv3AuthProtocol, snmpv3PrivProtocol, + mikrotikEnabled, mikrotikUsername, mikrotikPort, mikrotikSshPort, mikrotikUseSsl, + defaultAgentTokenId, insertedAt, updatedAt +
+ +id, email, role, isDefault, userId, insertedAt
+ ++ id, provider, enabled, syncIntervalMinutes, lastSyncedAt, lastSyncStatus, organizationId, insertedAt, updatedAt +
+ +summary, detail, timestamp, severity, icon, deviceName, siteName, link, type
+ +
+ DeleteResult: success (Boolean!), message (String)
+ TestResult: success (Boolean!), message (String)
+ MetricPoint: timestamp, value, status, checkName, checkType
+ Interface: id, ifIndex, ifName, ifDescr, ifType, ifSpeed, ifAdminStatus, ifOperStatus, ifAlias
+ Invitation: id, email, role, expiresAt, acceptedAt, insertedAt
+ AgentWithToken: id, name, enabled, token, insertedAt
+
curl -X POST https://app.towerops.net/api/graphql \
+ -H "Authorization: Bearer <%= @sample_token %>" \
+ -H "Content-Type: application/json" \
+ -d '{"query": "{ devices { id name ipAddress status site { id name location } } }"}'
+ curl -X POST https://app.towerops.net/api/graphql \
+ -H "Authorization: Bearer <%= @sample_token %>" \
+ -H "Content-Type: application/json" \
+ -d '{"query": "{ alerts(status: \"active\") { id alertType message triggeredAt device { name ipAddress } } }"}'
+ curl -X POST https://app.towerops.net/api/graphql \
+ -H "Authorization: Bearer <%= @sample_token %>" \
+ -H "Content-Type: application/json" \
+ -d '{"query": "mutation { createDevice(input: { ipAddress: \"192.168.1.1\", name: \"Core Router\", snmpEnabled: true }) { id name status } }"}'
+ curl -X POST https://app.towerops.net/api/graphql \
+ -H "Authorization: Bearer <%= @sample_token %>" \
+ -H "Content-Type: application/json" \
+ -d '{"query": "mutation { acknowledgeAlert(id: \"alert-uuid\") { id acknowledgedAt } }"}'
+ curl -X POST https://app.towerops.net/api/graphql \
+ -H "Authorization: Bearer <%= @sample_token %>" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "query": "query GetDevice($id: ID!) { device(id: $id) { id name ipAddress status site { name } } }",
+ "variables": {"id": "device-uuid"}
+ }'
+