Skip to content

Commit d08c2b9

Browse files
feat(backend): add files route, filter files, repository files view
Signed-off-by: roman-kiselenko <roman.kiselenko.dev@gmail.com>
1 parent abe5d18 commit d08c2b9

10 files changed

Lines changed: 164 additions & 133 deletions

File tree

README.md

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
21
<a href="https://en.wiktionary.org/wiki/smol"><img align="left" src="assets/smol-kitten.jpg" alt="a smol cat by Ron whisky" width="150" height="100" /></a>
32

43
**smolgit** offers a minimalist [git](https://git-scm.com/) server, making it perfect for small teams or individual developers. Its minimal simple and just works. It's perfect for those who value simplicity and efficiency in their workflow. Small memory footprint, one binary to go.
54

65
<!-- toc -->
6+
77
- [Features](#features)
88
- [Preview](#preview)
99
- [Getting Started](#getting-started)
@@ -23,25 +23,22 @@
2323
1. **user management** - simple user management, add users with `ssh-keys` to `config.yaml`.
2424
1. **permissions** - assign persmissions to user.
2525
1. **ligh-dark** - web theme based on your system settings.
26-
1. **basic-auth** - web basic auth middleware.
26+
1. **font** - customized fonts.
27+
1. **auth** - web auth middleware.
2728

2829
### Preview
2930

3031
<p align="center">
3132
<img src="assets/web_1.png" alt="screenshot" width="700" />
3233
</p>
33-
<p align="center">
34-
<img src="assets/web_2.png" alt="screenshot" width="700" />
35-
</p>
36-
3734

3835
### Getting Started
3936

4037
#### Install
4138

4239
1. Download binary from [ release page ](https://github.com/roman-kiselenko/smolgit/releases).
4340
1. Generate default `config.yaml` file with command `./smolgit config > config.yaml`.
44-
- Use [`yq`](https://github.com/mikefarah/yq) for inline changes `./smolgit config | yq '.server.disabled = true' > config.yaml`
41+
- Use [`yq`](https://github.com/mikefarah/yq) for inline changes `./smolgit config | yq '.server.disabled = true' > config.yaml`
4542
1. Run `./smolgit`
4643

4744
```shell
@@ -93,15 +90,15 @@ git:
9390
base: "git@my-git-server.lan"
9491
users:
9592
# User name used for folder in git.path
96-
- name: "bob"
97-
# Permissions, wildcard or regex
98-
# User to check access for other repositories
99-
# '*' - access for all repositories
100-
# 'admin' - access for admin's repositories
101-
# '(admin|billy)' - access for admin's and billy's repositories
102-
permissions: "*"
103-
keys:
104-
- ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCq9rD9b8tYyuSLsTECHCn... developer@mail.com
93+
- name: "bob"
94+
# Permissions, wildcard or regex
95+
# User to check access for other repositories
96+
# '*' - access for all repositories
97+
# 'admin' - access for admin's repositories
98+
# '(admin|billy)' - access for admin's and billy's repositories
99+
permissions: "*"
100+
keys:
101+
- ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCq9rD9b8tYyuSLsTECHCn... developer@mail.com
105102
```
106103
107104
cli options:
@@ -143,7 +140,8 @@ docker run -it -p 3080:3080 -p 3081:3081 -v /path-to-smolgit-project/smolgit/:/e
143140
- [golang](https://go.dev/)
144141
- [gin](https://github.com/gin-gonic/gin)
145142
- [go-git](https://github.com/go-git/go-git)
146-
- [pico](https://picocss.com/docs)
143+
- **React** - responsive and modern frontend.
144+
- **[shadcn/ui](https://ui.shadcn.com/)** + **Tailwind CSS** - clean and flexible UI components.
147145
- [gossh](https://github.com/gliderlabs/ssh)
148146

149147
### Local development

assets/web_1.png

87.2 KB
Loading

assets/web_2.png

-185 KB
Binary file not shown.

cmd/app.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -142,9 +142,7 @@ func (a *App) initWebServer(staticFiles embed.FS) error {
142142
})
143143

144144
router.GET("/api/repos", r.Repos)
145-
// router.GET("/repo/log/:user/:path", r.Log)
146-
// router.GET("/repo/files/:user/:path", r.Files)
147-
// router.GET("/repo/refs/:user/:path", r.Refs)
145+
router.GET("/api/repos/files/:user/:path", r.Files)
148146

149147
addr := a.Config.ServerAddr
150148
go func() {

frontend/src/components/pages/Repos/Table/ColumnDef.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { FolderGit2, Download } from 'lucide-react';
1+
import { Download } from 'lucide-react';
22
import { Link } from 'react-router-dom';
33
import { Button } from '@/components/ui/button';
44
import { ColumnDef } from '@tanstack/react-table';
@@ -10,9 +10,10 @@ const columns: ColumnDef<Repo>[] = [
1010
header: 'Repo',
1111
cell: ({ row }) => {
1212
return (
13-
<Link to={`/resource/${row.original.user.name}/${row.original.path}`}
14-
className="flex flex-row justify-start items-center">
15-
<FolderGit2 className="px-1" />
13+
<Link
14+
to={`/resource/${row.original.user.name}/${row.original.path}`}
15+
className="flex flex-row justify-start items-center"
16+
>
1617
<div className="px-1 align-middle">{row.original.path.replace(/\.git/, '')}</div>
1718
</Link>
1819
);
@@ -21,7 +22,7 @@ const columns: ColumnDef<Repo>[] = [
2122
{
2223
accessorKey: 'user.name',
2324
id: 'user',
24-
header: 'User',
25+
header: 'Owner',
2526
cell: ({ row }) => {
2627
return <div>{row.original?.user.name}</div>;
2728
},

frontend/src/components/pages/Start.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export function StartPage() {
4949
</div>
5050

5151
<div className="grid grid-cols-1">
52-
<div className="h-24 col-span-2">
52+
<div className="mx-3 h-24 col-span-2">
5353
<DataTable
5454
menuDisabled={true}
5555
kind={'repos'}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { Clock } from 'lucide-react';
2+
import AgeCell from '@/components/ui/Table/AgeCell';
3+
import { ColumnDef } from '@tanstack/react-table';
4+
5+
const columns: ColumnDef<string>[] = [
6+
{
7+
accessorKey: '',
8+
id: 'name',
9+
header: 'File',
10+
cell: ({ row }) => {
11+
return <div className="px-1 align-middle">{row.original.filename}</div>;
12+
},
13+
},
14+
{
15+
accessorKey: 'time',
16+
id: 'time',
17+
header: ({ column }) => <Clock size={12} />,
18+
cell: ({ row }) => {
19+
return (
20+
<div className="flex flex-row justify-start">
21+
<AgeCell age={'Nov 9, 2025, 11:38 AM GMT+3'} />
22+
</div>
23+
);
24+
},
25+
},
26+
];
27+
28+
export default columns;

frontend/src/components/views/RepoViewPage.tsx

Lines changed: 36 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,58 @@
11
import { useParams, Link } from 'react-router-dom';
2-
import { useEffect } from 'react';
2+
import { LogOut } from 'lucide-react';
3+
import { useEffect, useState } from 'react';
34
import { useReposState, getRepos } from '@/store/repositories';
4-
import { toast } from 'sonner';
5+
import { useRepoFilesState, getRepoFiles } from '@/store/repofiles';
56
import { Repo } from '@/types';
7+
import { DataTable } from '@/components/ui/DataTable';
8+
import columns from '@/components/views/ColumnDef';
9+
import { Input } from '@/components/ui/input';
10+
import { useAuth } from '@/context/AuthProvider';
11+
import { Button } from '@/components/ui/button';
612

713
export function RepoViewPage() {
8-
const { repoPath } = useParams<{ repoPath: string }>();
14+
const { user, repoPath } = useParams<{ user: string; repoPath: string }>();
915
const repos = useReposState();
16+
const repoFiles = useRepoFilesState();
17+
const [searchQuery, setSearchQuery] = useState('');
18+
const { logout, AuthDisabled } = useAuth();
1019

1120
useEffect(() => {
1221
if (!repoPath) return;
13-
// fetch all repos if not already loaded
1422
if (!repos.repos.get().length) {
1523
getRepos('');
1624
}
1725
}, [repoPath]);
1826

19-
const repoItem: Repo | any = repos.repos.get().find((r: Repo | any) => r.path === repoPath);
20-
21-
if (!repoItem) {
22-
return (
23-
<div className="p-4">
24-
<h2 className="text-xl font-bold">Repository not found</h2>
25-
<Link to="/resource" className="text-blue-500">
26-
Back to list
27-
</Link>
28-
</div>
29-
);
30-
}
27+
useEffect(() => {
28+
if (!user || !repoPath) return;
29+
getRepoFiles(searchQuery, user, repoPath);
30+
}, [user, searchQuery]);
3131

3232
return (
3333
<div className="flex-grow overflow-auto">
34-
<div className="flex flex-row py-2 px-2 items-center justify-between"></div>
34+
<div className="flex flex-row py-2 px-2 items-center justify-between">
35+
<Input
36+
placeholder="Filter by name..."
37+
className="placeholder:text-muted-foreground flex h-6 w-full rounded-md bg-transparent py-2 text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50"
38+
onChange={(e) => setSearchQuery(e.target.value)}
39+
/>
40+
{!AuthDisabled && (
41+
<Button onClick={logout} className="ml-2 text-xs">
42+
<LogOut size={12} />
43+
</Button>
44+
)}
45+
</div>
3546

3647
<div className="grid grid-cols-1">
37-
<div className="p-4">
38-
<h2 className="text-2xl font-bold mb-4">{repoItem.name}</h2>
39-
<p className="mb-2">
40-
<strong>Path:</strong> {repoItem.path}
41-
</p>
42-
<p className="mb-2">
43-
<strong>User:</strong> {repoItem.user?.name}
44-
</p>
45-
<Link to="/resource" className="text-blue-500">
46-
Back to list
47-
</Link>
48+
<div className="mx-3">
49+
<DataTable
50+
menuDisabled={true}
51+
kind="repo"
52+
noResult={false}
53+
columns={columns as any}
54+
data={repoFiles.files.get() as any}
55+
/>
4856
</div>
4957
</div>
5058
</div>

frontend/src/store/repofiles.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { hookstate, useHookstate } from '@hookstate/core';
2+
import { toast } from 'sonner';
3+
import { call } from '@/lib/api';
4+
5+
export const repoFilesState = hookstate<{ files: object[] }>({
6+
files: [],
7+
});
8+
9+
export async function getRepoFiles(query: string, userName: string | undefined, repoName: string | undefined) {
10+
try {
11+
let { files } = await call<any[]>(`repos/files/${userName}/${repoName}`);
12+
if (query !== '') {
13+
files = files.filter((c) => {
14+
return String(c.filename || '')
15+
.toLowerCase()
16+
.includes(query.toLowerCase());
17+
});
18+
}
19+
repoFilesState.files.set(files);
20+
} catch (error: any) {
21+
toast.error('Error! Cant load files\n' + error.message);
22+
console.error('Error! Cant load files\n' + error.message);
23+
}
24+
}
25+
26+
export function useRepoFilesState() {
27+
return useHookstate(repoFilesState);
28+
}

0 commit comments

Comments
 (0)