Skip to content
This repository was archived by the owner on Oct 13, 2025. It is now read-only.

Commit e718fd6

Browse files
committed
v0.6.0 ✨ Categories and current download graphs ! 🥳
2 parents c993d32 + 44a7df5 commit e718fd6

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+18123
-12070
lines changed

.github/workflows/docker.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ jobs:
5252
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }}
5353
# If source files changed but packages didn't, rebuild from a prior cache.
5454
restore-keys: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-
55-
- run: yarn install --frozen-lockfile
55+
- run: yarn install --immutable
5656
- run: yarn build
5757
- name: Cache build output
5858
# to copy needed files to docker build job

.github/workflows/docker_dev.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ jobs:
6262
# If source files changed but packages didn't, rebuild from a prior cache.
6363
restore-keys: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-
6464

65-
- run: yarn install --frozen-lockfile
65+
- run: yarn install --immutable
6666
- run: yarn build
6767

6868
- name: Cache build output

.gitignore

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,14 @@ yarn-error.log*
3636

3737
# storybook
3838
storybook-static
39-
data/configs
39+
data/configs
40+
41+
# https://yarnpkg.com/getting-started/qa#which-files-should-be-gitignored
42+
# Yarn v2
43+
.pnp.*
44+
.yarn/*
45+
!.yarn/patches
46+
!.yarn/plugins
47+
!.yarn/releases
48+
!.yarn/sdks
49+
!.yarn/versions

.yarn/releases/yarn-3.2.1.cjs

Lines changed: 786 additions & 0 deletions
Large diffs are not rendered by default.

.yarnrc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2+
# yarn lockfile v1
3+
4+
5+
yarn-path ".yarn/releases/yarn-1.22.19.cjs"

.yarnrc.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
nodeLinker: node-modules
2+
3+
yarnPath: .yarn/releases/yarn-3.2.1.cjs

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
<i>Join the discord! — Don't forget to star the repo if you are enjoying the project!</i>
2222
</p>
2323
<p align="center">
24-
<a href="https://homarr.netlify.app/"><strong> Demo ↗️ </strong></a> • <a href="#-installation"><strong> Install ➡️ </strong></a> • <a href="https://github.com/ajnart/homarr/wiki"><strong> Read the Wiki 📄 </strong></a>
24+
<a href="https://homarr.ajnart.fr/"><strong> Demo ↗️ </strong></a> • <a href="#-installation"><strong> Install ➡️ </strong></a> • <a href="https://github.com/ajnart/homarr/wiki"><strong> Read the Wiki 📄 </strong></a>
2525
</p>
2626

2727
---

data/constants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
export const REPO_URL = 'ajnart/homarr';
2-
export const CURRENT_VERSION = 'v0.5.2';
2+
export const CURRENT_VERSION = 'v0.6.0';

package.json

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
{
22
"name": "homarr",
3-
"version": "0.5.2",
4-
"private": "false",
3+
"version": "0.6.0",
54
"description": "Homarr - A homepage for your server.",
65
"repository": {
76
"type": "git",
@@ -27,8 +26,10 @@
2726
"dependencies": {
2827
"@ctrl/deluge": "^4.0.0",
2928
"@ctrl/qbittorrent": "^4.0.0",
29+
"@ctrl/shared-torrent": "^4.1.0",
3030
"@dnd-kit/core": "^6.0.1",
3131
"@dnd-kit/sortable": "^7.0.0",
32+
"@dnd-kit/utilities": "^3.2.0",
3233
"@mantine/core": "^4.2.6",
3334
"@mantine/dates": "^4.2.6",
3435
"@mantine/dropzone": "^4.2.6",
@@ -37,6 +38,9 @@
3738
"@mantine/next": "^4.2.6",
3839
"@mantine/notifications": "^4.2.6",
3940
"@mantine/prism": "^4.2.6",
41+
"@nivo/core": "^0.79.0",
42+
"@nivo/line": "^0.79.1",
43+
"@tabler/icons": "^1.68.0",
4044
"axios": "^0.27.2",
4145
"cookies-next": "^2.0.4",
4246
"dayjs": "^1.11.2",
@@ -46,7 +50,7 @@
4650
"prism-react-renderer": "^1.3.1",
4751
"react": "^17.0.1",
4852
"react-dom": "^17.0.1",
49-
"tabler-icons-react": "^1.46.0",
53+
"systeminformation": "^5.11.16",
5054
"uuid": "^8.3.2"
5155
},
5256
"devDependencies": {
@@ -78,5 +82,6 @@
7882
},
7983
"resolutions": {
8084
"@types/react": "17.0.30"
81-
}
85+
},
86+
"packageManager": "[email protected]"
8287
}

src/components/AppShelf/AddAppShelfItem.tsx

Lines changed: 94 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@ import {
1010
ActionIcon,
1111
Tooltip,
1212
Title,
13+
Anchor,
14+
Text,
1315
} from '@mantine/core';
1416
import { useForm } from '@mantine/form';
1517
import { useState } from 'react';
16-
import { Apps } from 'tabler-icons-react';
18+
import { IconApps as Apps } from '@tabler/icons';
1719
import { v4 as uuidv4 } from 'uuid';
1820
import { useConfig } from '../../tools/state';
1921
import { ServiceTypeList } from '../../tools/types';
@@ -61,15 +63,48 @@ function MatchIcon(name: string, form: any) {
6163
return false;
6264
}
6365

66+
function MatchService(name: string, form: any) {
67+
const service = ServiceTypeList.find((s) => s === name);
68+
if (service) {
69+
form.setFieldValue('type', service);
70+
}
71+
}
72+
73+
function MatchPort(name: string, form: any) {
74+
const portmap = [
75+
{ name: 'qBittorrent', value: '8080' },
76+
{ name: 'Sonarr', value: '8989' },
77+
{ name: 'Radarr', value: '7878' },
78+
{ name: 'Lidarr', value: '8686' },
79+
{ name: 'Readarr', value: '8686' },
80+
{ name: 'Deluge', value: '8112' },
81+
{ name: 'Transmission', value: '9091' },
82+
];
83+
// Match name with portmap key
84+
const port = portmap.find((p) => p.name === name);
85+
if (port) {
86+
form.setFieldValue('url', `http://localhost:${port.value}`);
87+
}
88+
}
89+
6490
export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } & any) {
6591
const { setOpened } = props;
6692
const { config, setConfig } = useConfig();
6793
const [isLoading, setLoading] = useState(false);
6894

95+
// Extract all the categories from the services in config
96+
const categoryList = config.services.reduce((acc, cur) => {
97+
if (cur.category && !acc.includes(cur.category)) {
98+
acc.push(cur.category);
99+
}
100+
return acc;
101+
}, [] as string[]);
102+
69103
const form = useForm({
70104
initialValues: {
71105
id: props.id ?? uuidv4(),
72106
type: props.type ?? 'Other',
107+
category: props.category ?? undefined,
73108
name: props.name ?? '',
74109
icon: props.icon ?? '/favicon.svg',
75110
url: props.url ?? '',
@@ -99,6 +134,15 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } &
99134
},
100135
});
101136

137+
// Try to set const hostname to new URL(form.values.url).hostname)
138+
// If it fails, set it to the form.values.url
139+
let hostname = form.values.url;
140+
try {
141+
hostname = new URL(form.values.url).origin;
142+
} catch (e) {
143+
// Do nothing
144+
}
145+
102146
return (
103147
<>
104148
<Center>
@@ -145,10 +189,9 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } &
145189
value={form.values.name}
146190
onChange={(event) => {
147191
form.setFieldValue('name', event.currentTarget.value);
148-
const match = MatchIcon(event.currentTarget.value, form);
149-
if (match) {
150-
form.setFieldValue('icon', match);
151-
}
192+
MatchIcon(event.currentTarget.value, form);
193+
MatchService(event.currentTarget.value, form);
194+
MatchPort(event.currentTarget.value, form);
152195
}}
153196
error={form.errors.name && 'Invalid icon url'}
154197
/>
@@ -166,29 +209,64 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } &
166209
{...form.getInputProps('url')}
167210
/>
168211
<Select
169-
label="Select the type of service (used for API calls)"
212+
label="Service type"
170213
defaultValue="Other"
171214
placeholder="Pick one"
172215
required
173216
searchable
174217
data={ServiceTypeList}
175218
{...form.getInputProps('type')}
176219
/>
220+
<Select
221+
label="Category"
222+
data={categoryList}
223+
placeholder="Select a category or create a new one"
224+
nothingFound="Nothing found"
225+
searchable
226+
clearable
227+
creatable
228+
onClick={(e) => {
229+
e.preventDefault();
230+
}}
231+
getCreateLabel={(query) => `+ Create "${query}"`}
232+
onCreate={(query) => {}}
233+
{...form.getInputProps('category')}
234+
/>
177235
<LoadingOverlay visible={isLoading} />
178236
{(form.values.type === 'Sonarr' ||
179237
form.values.type === 'Radarr' ||
180238
form.values.type === 'Lidarr' ||
181239
form.values.type === 'Readarr') && (
182-
<TextInput
183-
required
184-
label="API key"
185-
placeholder="Your API key"
186-
value={form.values.apiKey}
187-
onChange={(event) => {
188-
form.setFieldValue('apiKey', event.currentTarget.value);
189-
}}
190-
error={form.errors.apiKey && 'Invalid API key'}
191-
/>
240+
<>
241+
<TextInput
242+
required
243+
label="API key"
244+
placeholder="Your API key"
245+
value={form.values.apiKey}
246+
onChange={(event) => {
247+
form.setFieldValue('apiKey', event.currentTarget.value);
248+
}}
249+
error={form.errors.apiKey && 'Invalid API key'}
250+
/>
251+
<Text
252+
style={{
253+
alignSelf: 'center',
254+
fontSize: '0.75rem',
255+
textAlign: 'center',
256+
color: 'gray',
257+
}}
258+
>
259+
Tip: Get your API key{' '}
260+
<Anchor
261+
target="_blank"
262+
weight="bold"
263+
style={{ fontStyle: 'inherit', fontSize: 'inherit' }}
264+
href={`${hostname}/settings/general`}
265+
>
266+
here.
267+
</Anchor>
268+
</Text>
269+
</>
192270
)}
193271
{form.values.type === 'qBittorrent' && (
194272
<>

0 commit comments

Comments
 (0)