Skip to content

Commit

Permalink
progress 1
Browse files Browse the repository at this point in the history
  • Loading branch information
Mubashir Shariq authored and Mubashir Shariq committed Sep 21, 2024
1 parent 64c806d commit b88145f
Show file tree
Hide file tree
Showing 6 changed files with 377 additions and 1 deletion.
9 changes: 9 additions & 0 deletions keep-ui/app/runbooks/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import RunbookIncidentTable from './runbook-table';

export default function RunbookPage() {
return (
<div>
<RunbookIncidentTable />
</div>
);
}
177 changes: 177 additions & 0 deletions keep-ui/app/runbooks/runbook-table.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
"use client";

import React, { useState } from "react";
import Modal from "react-modal"; // Add this import for react-modal
import {
Button,
Badge,
Table as TremorTable,
TableBody,
TableCell,
TableHead,
TableHeaderCell,
TableRow,
} from "@tremor/react";
import { DisplayColumnDef } from "@tanstack/react-table";
import { GenericTable } from "@/components/table/GenericTable";


const customStyles = {
content: {
top: '50%',
left: '50%',
right: 'auto',
bottom: 'auto',
marginRight: '-50%',
transform: 'translate(-50%, -50%)',
width: '400px',
},
};

interface Incident {
id: number;
name: string;
}

interface Runbook {
id: number;
title: string;
incidents: Incident[];
}

const runbookData: Runbook[] = [
{
id: 1,
title: "Database Recovery",
incidents: [
{ id: 101, name: "DB Outage on 2024-01-01" },
{ id: 102, name: "DB Backup Failure" },
],
},
{
id: 2,
title: "API Health Check",
incidents: [
{ id: 201, name: "API Latency Issue" },
],
},
{
id: 3,
title: "Server Restart Guide",
incidents: [
{ id: 301, name: "Unexpected Server Crash" },
{ id: 302, name: "Scheduled Maintenance" },
],
},
];

const columns: DisplayColumnDef<Runbook>[] = [
{
accessorKey: 'title',
header: 'Runbook Title',
cell: info => info.getValue(),
},
{
accessorKey: 'incidents',
header: 'Incidents',
cell: info => (
<div>
{info.getValue().map((incident: Incident) => (
<Badge key={incident.id} color="green" className="mr-2 mb-1">
{incident.name}
</Badge>
))}
</div>
),
},
];

function RunbookIncidentTable() {
const [offset, setOffset] = useState(0);
const [limit, setLimit] = useState(10);

// Modal state management
const [isModalOpen, setIsModalOpen] = useState(false);
const [repositoryName, setRepositoryName] = useState('');
const [pathToMdFiles, setPathToMdFiles] = useState('');

const handlePaginationChange = (newLimit: number, newOffset: number) => {
setLimit(newLimit);
setOffset(newOffset);
};

// Open modal handler
const openModal = () => {
setIsModalOpen(true);
};

// Close modal handler
const closeModal = () => {
setIsModalOpen(false);
};

// Handle save action from modal
const handleSave = () => {
// You can handle saving the data here (e.g., API call or updating state)
console.log('Repository:', repositoryName);
console.log('Path to MD Files:', pathToMdFiles);
closeModal();
};

return (
<div>
<Button onClick={openModal}>Settings</Button>

<GenericTable<Runbook>
data={runbookData}
columns={columns}
rowCount={runbookData.length}
offset={offset}
limit={limit}
onPaginationChange={handlePaginationChange}
onRowClick={(row) => {
console.log("Runbook clicked:", row);
}}
/>

{/* Modal for Settings */}
<Modal
isOpen={isModalOpen}
onRequestClose={closeModal}
style={customStyles}
contentLabel="Settings Modal"
>
<h2>Runbook Settings</h2>

<div>
<label>Repository Name</label>
<input
type="text"
value={repositoryName}
onChange={(e) => setRepositoryName(e.target.value)}
placeholder="Enter repository name"
style={{ width: '100%', padding: '8px', marginBottom: '10px' }}
/>
</div>

<div>
<label>Path to MD Files</label>
<input
type="text"
value={pathToMdFiles}
onChange={(e) => setPathToMdFiles(e.target.value)}
placeholder="Enter path to markdown files"
style={{ width: '100%', padding: '8px', marginBottom: '10px' }}
/>
</div>

<div style={{ textAlign: 'right' }}>
<Button onClick={closeModal} style={{ marginRight: '10px' }}>Cancel</Button>
<Button onClick={handleSave} color="blue">Save</Button>
</div>
</Modal>
</div>
);
}

export default RunbookIncidentTable;
9 changes: 9 additions & 0 deletions keep-ui/components/navbar/NoiseReductionLinks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import classNames from "classnames";
import { AILink } from "./AILink";
import { TbTopologyRing } from "react-icons/tb";
import { FaVolumeMute } from "react-icons/fa";
import { FaMarkdown } from "react-icons/fa";
import { useTopology } from "utils/hooks/useTopology";

type NoiseReductionLinksProps = { session: Session | null };
Expand Down Expand Up @@ -41,6 +42,14 @@ export const NoiseReductionLinks = ({ session }: NoiseReductionLinksProps) => {
</Disclosure.Button>

<Disclosure.Panel as="ul" className="space-y-2 p-2 pr-4">
<li >
<LinkWithIcon
href="/runbooks"
icon={FaMarkdown}
>
<Subtitle>Runbooks</Subtitle>
</LinkWithIcon>
</li>
<li>
<LinkWithIcon href="/rules" icon={Rules}>
<Subtitle>Correlations</Subtitle>
Expand Down
92 changes: 92 additions & 0 deletions keep/api/models/db/runbook.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
from datetime import datetime
from typing import List, Optional
from uuid import UUID, uuid4

from pydantic import BaseModel
from sqlalchemy import DateTime, ForeignKey, Column, TEXT, JSON
from sqlmodel import Field, Relationship, SQLModel
from keep.api.models.db.tenant import Tenant

# Runbook Model
class Runbook(SQLModel, table=True):
id: UUID = Field(default_factory=uuid4, primary_key=True)
tenant_id: str = Field(foreign_key="tenant.id")
tenant: Tenant = Relationship()

title: str = Field(nullable=False) # Title of the runbook
link: str = Field(nullable=False) # Link to the .md file

incidents: List["Incident"] = Relationship(
back_populates="runbooks", link_model=RunbookToIncident
)

created_at: datetime = Field(default_factory=datetime.utcnow)

class Config:
arbitrary_types_allowed = True


# Link Model between Runbook and Incident
class RunbookToIncident(SQLModel, table=True):
tenant_id: str = Field(foreign_key="tenant.id")
runbook_id: UUID = Field(foreign_key="runbook.id", primary_key=True)
incident_id: UUID = Field(foreign_key="incident.id", primary_key=True)

incident_id: UUID = Field(
sa_column=Column(
UUID(binary=False),
ForeignKey("incident.id", ondelete="CASCADE"),
primary_key=True,
)
)


# Incident Model
class Incident(SQLModel, table=True):
id: UUID = Field(default_factory=uuid4, primary_key=True)
tenant_id: str = Field(foreign_key="tenant.id")
tenant: Tenant = Relationship()

user_generated_name: Optional[str] = None
ai_generated_name: Optional[str] = None

user_summary: Optional[str] = Field(sa_column=Column(TEXT), nullable=True)
generated_summary: Optional[str] = Field(sa_column=Column(TEXT), nullable=True)

assignee: Optional[str] = None
severity: int = Field(default=IncidentSeverity.CRITICAL.order)

creation_time: datetime = Field(default_factory=datetime.utcnow)

start_time: Optional[datetime] = None
end_time: Optional[datetime] = None
last_seen_time: Optional[datetime] = None

runbooks: List["Runbook"] = Relationship(
back_populates="incidents", link_model=RunbookToIncident
)

is_predicted: bool = Field(default=False)
is_confirmed: bool = Field(default=False)

alerts_count: int = Field(default=0)
affected_services: List = Field(sa_column=Column(JSON), default_factory=list)
sources: List = Field(sa_column=Column(JSON), default_factory=list)

rule_id: Optional[UUID] = Field(
sa_column=Column(
UUID(binary=False),
ForeignKey("rule.id", ondelete="CASCADE"),
nullable=True,
),
)

rule_fingerprint: str = Field(default="", sa_column=Column(TEXT))

def __init__(self, **kwargs):
super().__init__(**kwargs)
if "runbooks" not in kwargs:
self.runbooks = []

class Config:
arbitrary_types_allowed = True
47 changes: 46 additions & 1 deletion keep/providers/github_provider/github_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,20 @@ class GithubProviderAuthConfig:
"sensitive": True,
}
)
repository: str = dataclasses.field(
metadata={
"description": "GitHub Repository",
"sensitive": False,
},
default=None,
)
md_path: str = dataclasses.field(
metadata={
"description": "Path to .md files in the repository",
"sensitive": False,
},
default=None,
)


class GithubProvider(BaseProvider):
Expand Down Expand Up @@ -58,6 +72,32 @@ def validate_config(self):
self.authentication_config = GithubProviderAuthConfig(
**self.config.authentication
)
def query_runbook(self,query):
"""Retrieve markdown files from the GitHub repository."""

if not query:
raise ValueError("Query is required")

auth=None
if self.authentication_config.repository and self.authentication_config.md_path:
auth = HTTPBasicAuth(
self.authentication_config.repository,
self.authentication_config.md_path,
)

resp = requests.get(
f"{self.authentication_config.url}/api/v1/query",
params={"query": query},
auth=(
auth
if self.authentication_config.repository and self.authentication_config.md_path
else None
)
)
if response.status_code != 200:
raise Exception(f"Runbook Query Failed: {response.content}")

return response.json()


class GithubStarsProvider(GithubProvider):
Expand Down Expand Up @@ -111,7 +151,12 @@ def _query(
github_stars_provider = GithubStarsProvider(
context_manager,
"test",
ProviderConfig(authentication={"access_token": os.environ.get("GITHUB_PAT")}),
ProviderConfig(authentication={
"access_token": os.environ.get("GITHUB_PAT"),
"repository": os.environ.get("GITHUB_REPOSITORY"),
"md_path": os.environ.get("MARKDOWN_PATH"),
}
),
)

result = github_stars_provider.query(
Expand Down
Loading

0 comments on commit b88145f

Please sign in to comment.