mirror of
https://github.com/gethomepage/homepage.git
synced 2025-12-05 21:47:48 +01:00
Compare commits
7 Commits
main
...
7d78a99bc2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d78a99bc2 | ||
|
|
4031178831 | ||
|
|
b65c8399d8 | ||
|
|
6b63cfd491 | ||
|
|
196c51bf73 | ||
|
|
17c9b2631e | ||
|
|
1a21189643 |
2
.github/workflows/crowdin.yml
vendored
2
.github/workflows/crowdin.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
- name: crowdin action
|
- name: crowdin action
|
||||||
uses: crowdin/github-action@v2
|
uses: crowdin/github-action@v2
|
||||||
with:
|
with:
|
||||||
|
|||||||
4
.github/workflows/docker-publish.yml
vendored
4
.github/workflows/docker-publish.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
|||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Install python
|
- name: Install python
|
||||||
uses: actions/setup-python@v6
|
uses: actions/setup-python@v6
|
||||||
@@ -62,7 +62,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Extract Docker metadata
|
- name: Extract Docker metadata
|
||||||
id: meta
|
id: meta
|
||||||
|
|||||||
6
.github/workflows/docs-publish.yml
vendored
6
.github/workflows/docs-publish.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
|||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
- name: Install python
|
- name: Install python
|
||||||
uses: actions/setup-python@v6
|
uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
@@ -32,7 +32,7 @@ jobs:
|
|||||||
needs:
|
needs:
|
||||||
- pre-commit
|
- pre-commit
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
- uses: actions/setup-python@v6
|
- uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: 3.x
|
python-version: 3.x
|
||||||
@@ -54,7 +54,7 @@ jobs:
|
|||||||
needs:
|
needs:
|
||||||
- pre-commit
|
- pre-commit
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
- name: Configure Git Credentials
|
- name: Configure Git Credentials
|
||||||
run: |
|
run: |
|
||||||
git config user.name github-actions[bot]
|
git config user.name github-actions[bot]
|
||||||
|
|||||||
@@ -571,3 +571,18 @@ or per service widget (`services.yaml`) with:
|
|||||||
```
|
```
|
||||||
|
|
||||||
If either value is set to true, the error message will be hidden.
|
If either value is set to true, the error message will be hidden.
|
||||||
|
|
||||||
|
## Disable Search Engine Indexing
|
||||||
|
|
||||||
|
You can request that search engines not to index your Homepage instance by enabling the `disableIndexing` setting.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
disableIndexing: true
|
||||||
|
```
|
||||||
|
|
||||||
|
When enabled, this will:
|
||||||
|
|
||||||
|
- Disallow all crawlers in `robots.txt`
|
||||||
|
- Add `<meta name="robots" content="noindex, nofollow">` tags to prevent indexing
|
||||||
|
|
||||||
|
By default this feature is disabled.
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ Learn more about [Crowdsec](https://crowdsec.net).
|
|||||||
See the [crowdsec docs](https://docs.crowdsec.net/docs/local_api/intro/#machines) for information about registering a machine,
|
See the [crowdsec docs](https://docs.crowdsec.net/docs/local_api/intro/#machines) for information about registering a machine,
|
||||||
in most instances you can use the default credentials (`/etc/crowdsec/local_api_credentials.yaml`).
|
in most instances you can use the default credentials (`/etc/crowdsec/local_api_credentials.yaml`).
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
Without the `limit24h` option, the widget will fetch all alerts which is limited to 100 by the API to avoid performance issues.
|
||||||
|
|
||||||
Allowed fields: `["alerts", "bans"]`.
|
Allowed fields: `["alerts", "bans"]`.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
@@ -16,4 +19,5 @@ widget:
|
|||||||
url: http://crowdsechostorip:port
|
url: http://crowdsechostorip:port
|
||||||
username: localhost # machine_id in crowdsec
|
username: localhost # machine_id in crowdsec
|
||||||
password: password
|
password: password
|
||||||
|
limit24h: true # optional, limits alerts to last 24h. Default: false
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ See the [official docs](https://github.com/ghostfolio/ghostfolio#authorization-b
|
|||||||
|
|
||||||
_Note that the Bearer token is valid for 6 months, after which a new one must be generated._
|
_Note that the Bearer token is valid for 6 months, after which a new one must be generated._
|
||||||
|
|
||||||
Allowed fields: `["gross_percent_today", "gross_percent_1y", "gross_percent_max"]`
|
Allowed fields: `["gross_percent_today", "gross_percent_1y", "gross_percent_max", "net_worth"]`
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
widget:
|
widget:
|
||||||
|
|||||||
@@ -759,7 +759,8 @@
|
|||||||
"ghostfolio": {
|
"ghostfolio": {
|
||||||
"gross_percent_today": "Today",
|
"gross_percent_today": "Today",
|
||||||
"gross_percent_1y": "One year",
|
"gross_percent_1y": "One year",
|
||||||
"gross_percent_max": "All time"
|
"gross_percent_max": "All time",
|
||||||
|
"net_worth": "Net Worth"
|
||||||
},
|
},
|
||||||
"audiobookshelf": {
|
"audiobookshelf": {
|
||||||
"podcasts": "Podcasts",
|
"podcasts": "Podcasts",
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ export default function Error({ error }) {
|
|||||||
|
|
||||||
if (typeof error === "string") {
|
if (typeof error === "string") {
|
||||||
error = { message: error }; // eslint-disable-line no-param-reassign
|
error = { message: error }; // eslint-disable-line no-param-reassign
|
||||||
|
} else if (typeof error === "number") {
|
||||||
|
error = { message: `Error ${error}` }; // eslint-disable-line no-param-reassign
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error?.data?.error) {
|
if (error?.data?.error) {
|
||||||
|
|||||||
@@ -400,6 +400,7 @@ function Home({ initialSettings }) {
|
|||||||
"A highly customizable homepage (or startpage / application dashboard) with Docker and service API integrations."
|
"A highly customizable homepage (or startpage / application dashboard) with Docker and service API integrations."
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
{settings.disableIndexing && <meta name="robots" content="noindex, nofollow" />}
|
||||||
{settings.base && <base href={settings.base} />}
|
{settings.base && <base href={settings.base} />}
|
||||||
{settings.favicon ? (
|
{settings.favicon ? (
|
||||||
<>
|
<>
|
||||||
|
|||||||
19
src/pages/robots.txt.js
Normal file
19
src/pages/robots.txt.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { getSettings } from "utils/config/config";
|
||||||
|
|
||||||
|
export async function getServerSideProps({ res }) {
|
||||||
|
const settings = getSettings();
|
||||||
|
const content = ["User-agent: *", !!settings.disableIndexing ? "Disallow: /" : "Allow: /"].join("\n");
|
||||||
|
|
||||||
|
res.setHeader("Content-Type", "text/plain");
|
||||||
|
res.write(content);
|
||||||
|
res.end();
|
||||||
|
|
||||||
|
return {
|
||||||
|
props: {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function RobotsTxt() {
|
||||||
|
// placeholder component
|
||||||
|
return null;
|
||||||
|
}
|
||||||
@@ -279,6 +279,9 @@ export function cleanServiceGroups(groups) {
|
|||||||
slugs,
|
slugs,
|
||||||
symbols,
|
symbols,
|
||||||
|
|
||||||
|
// crowdsec
|
||||||
|
limit24h,
|
||||||
|
|
||||||
// customapi
|
// customapi
|
||||||
mappings,
|
mappings,
|
||||||
display,
|
display,
|
||||||
@@ -473,6 +476,10 @@ export function cleanServiceGroups(groups) {
|
|||||||
if (defaultinterval) widget.defaultinterval = defaultinterval;
|
if (defaultinterval) widget.defaultinterval = defaultinterval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (limit24h !== undefined) {
|
||||||
|
widget.limit24h = !!limit24h;
|
||||||
|
}
|
||||||
|
|
||||||
if (type === "docker") {
|
if (type === "docker") {
|
||||||
if (server) widget.server = server;
|
if (server) widget.server = server;
|
||||||
if (container) widget.container = container;
|
if (container) widget.container = container;
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export default function Component({ service }) {
|
|||||||
|
|
||||||
const { widget } = service;
|
const { widget } = service;
|
||||||
|
|
||||||
const { data: alerts, error: alertsError } = useWidgetAPI(widget, "alerts");
|
const { data: alerts, error: alertsError } = useWidgetAPI(widget, !!widget.limit24h ? "alerts24h" : "alerts");
|
||||||
const { data: bans, error: bansError } = useWidgetAPI(widget, "bans");
|
const { data: bans, error: bansError } = useWidgetAPI(widget, "bans");
|
||||||
|
|
||||||
if (alertsError || bansError) {
|
if (alertsError || bansError) {
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ const widget = {
|
|||||||
alerts: {
|
alerts: {
|
||||||
endpoint: "alerts",
|
endpoint: "alerts",
|
||||||
},
|
},
|
||||||
|
alerts24h: {
|
||||||
|
endpoint: "alerts?limit=0&since=24h",
|
||||||
|
},
|
||||||
bans: {
|
bans: {
|
||||||
endpoint: "alerts?decision_type=ban&origin=crowdsec&has_active_decision=1",
|
endpoint: "alerts?decision_type=ban&origin=crowdsec&has_active_decision=1",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -166,7 +166,11 @@ export default function Component({ service }) {
|
|||||||
refreshInterval: Math.max(1000, refreshInterval),
|
refreshInterval: Math.max(1000, refreshInterval),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (customError) {
|
// if mappings includes an error field and the data contains an error field then show data even if there is an error
|
||||||
|
const mappingsIncludesError = Array.isArray(mappings) && mappings.find((mapping) => mapping.field === "error");
|
||||||
|
const errorIsData = customData && typeof customData === "object" && "error" in customData;
|
||||||
|
|
||||||
|
if (customError && !(mappingsIncludesError && errorIsData)) {
|
||||||
return <Container service={service} error={customError} />;
|
return <Container service={service} error={customError} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,13 +20,15 @@ function getPerformancePercent(t, performanceRange) {
|
|||||||
export default function Component({ service }) {
|
export default function Component({ service }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { widget } = service;
|
const { widget } = service;
|
||||||
|
const includeNetWorth = widget.fields?.includes("net_worth");
|
||||||
|
|
||||||
const { data: performanceToday, error: ghostfolioErrorToday } = useWidgetAPI(widget, "today");
|
const { data: performanceToday, error: ghostfolioErrorToday } = useWidgetAPI(widget, "today");
|
||||||
const { data: performanceYear, error: ghostfolioErrorYear } = useWidgetAPI(widget, "year");
|
const { data: performanceYear, error: ghostfolioErrorYear } = useWidgetAPI(widget, "year");
|
||||||
const { data: performanceMax, error: ghostfolioErrorMax } = useWidgetAPI(widget, "max");
|
const { data: performanceMax, error: ghostfolioErrorMax } = useWidgetAPI(widget, "max");
|
||||||
|
const { data: userInfo, error: ghostfolioErrorUserInfo } = useWidgetAPI(widget, includeNetWorth ? "userInfo" : "");
|
||||||
|
|
||||||
if (ghostfolioErrorToday || ghostfolioErrorYear || ghostfolioErrorMax) {
|
if (ghostfolioErrorToday || ghostfolioErrorYear || ghostfolioErrorMax || ghostfolioErrorUserInfo) {
|
||||||
const finalError = ghostfolioErrorToday ?? ghostfolioErrorYear ?? ghostfolioErrorMax;
|
const finalError = ghostfolioErrorToday ?? ghostfolioErrorYear ?? ghostfolioErrorMax ?? ghostfolioErrorUserInfo;
|
||||||
return <Container service={service} error={finalError} />;
|
return <Container service={service} error={finalError} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,12 +36,13 @@ export default function Component({ service }) {
|
|||||||
return <Container service={service} error={performanceToday} />;
|
return <Container service={service} error={performanceToday} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!performanceToday || !performanceYear || !performanceMax) {
|
if (!performanceToday || !performanceYear || !performanceMax || (includeNetWorth && !userInfo)) {
|
||||||
return (
|
return (
|
||||||
<Container service={service}>
|
<Container service={service}>
|
||||||
<Block label="ghostfolio.gross_percent_today" />
|
<Block label="ghostfolio.gross_percent_today" />
|
||||||
<Block label="ghostfolio.gross_percent_1y" />
|
<Block label="ghostfolio.gross_percent_1y" />
|
||||||
<Block label="ghostfolio.gross_percent_max" />
|
<Block label="ghostfolio.gross_percent_max" />
|
||||||
|
{includeNetWorth && <Block label="ghostfolio.net_worth" />}
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -49,6 +52,12 @@ export default function Component({ service }) {
|
|||||||
<Block label="ghostfolio.gross_percent_today" value={getPerformancePercent(t, performanceToday)} />
|
<Block label="ghostfolio.gross_percent_today" value={getPerformancePercent(t, performanceToday)} />
|
||||||
<Block label="ghostfolio.gross_percent_1y" value={getPerformancePercent(t, performanceYear)} />
|
<Block label="ghostfolio.gross_percent_1y" value={getPerformancePercent(t, performanceYear)} />
|
||||||
<Block label="ghostfolio.gross_percent_max" value={getPerformancePercent(t, performanceMax)} />
|
<Block label="ghostfolio.gross_percent_max" value={getPerformancePercent(t, performanceMax)} />
|
||||||
|
{includeNetWorth && (
|
||||||
|
<Block
|
||||||
|
label="ghostfolio.net_worth"
|
||||||
|
value={`${performanceToday.performance.currentNetWorth.toFixed(2)} ${userInfo?.settings?.currency ?? ""}`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,21 @@
|
|||||||
import credentialedProxyHandler from "utils/proxy/handlers/credentialed";
|
import credentialedProxyHandler from "utils/proxy/handlers/credentialed";
|
||||||
|
|
||||||
const widget = {
|
const widget = {
|
||||||
api: "{url}/api/v2/portfolio/performance?range={endpoint}",
|
api: "{url}/api/{endpoint}",
|
||||||
proxyHandler: credentialedProxyHandler,
|
proxyHandler: credentialedProxyHandler,
|
||||||
|
|
||||||
mappings: {
|
mappings: {
|
||||||
today: {
|
today: {
|
||||||
endpoint: "1d",
|
endpoint: "v2/portfolio/performance?range=1d",
|
||||||
},
|
},
|
||||||
year: {
|
year: {
|
||||||
endpoint: "1y",
|
endpoint: "v2/portfolio/performance?range=1y",
|
||||||
},
|
},
|
||||||
max: {
|
max: {
|
||||||
endpoint: "max",
|
endpoint: "v2/portfolio/performance?range=max",
|
||||||
|
},
|
||||||
|
userInfo: {
|
||||||
|
endpoint: "v1/user",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -24,9 +24,9 @@ export default function Component({ service }) {
|
|||||||
if (!data || (data && data.length === 0)) {
|
if (!data || (data && data.length === 0)) {
|
||||||
return (
|
return (
|
||||||
<Container service={service}>
|
<Container service={service}>
|
||||||
<Block label="myspeed.ping" />
|
|
||||||
<Block label="myspeed.download" />
|
<Block label="myspeed.download" />
|
||||||
<Block label="myspeed.upload" />
|
<Block label="myspeed.upload" />
|
||||||
|
<Block label="myspeed.ping" />
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user