Merge branch 'dev' of https://github.com/umami-software/umami into francis/uc-24-kafka-test

This commit is contained in:
Francis Cao 2022-08-04 17:48:00 -07:00
commit 1ffd5775f8
138 changed files with 2849 additions and 239 deletions

View File

@ -1 +0,0 @@
web: npm run start-env

View File

@ -6,10 +6,15 @@ import { setItem } from 'lib/web';
import { REPO_URL, VERSION_CHECK } from 'lib/constants'; import { REPO_URL, VERSION_CHECK } from 'lib/constants';
import Button from './Button'; import Button from './Button';
import styles from './UpdateNotice.module.css'; import styles from './UpdateNotice.module.css';
import useUser from 'hooks/useUser';
import useConfig from 'hooks/useConfig';
export default function UpdateNotice() { export default function UpdateNotice() {
const { user } = useUser();
const { updatesDisabled } = useConfig();
const { latest, checked, hasUpdate, releaseUrl } = useStore(); const { latest, checked, hasUpdate, releaseUrl } = useStore();
const [dismissed, setDismissed] = useState(false); const [dismissed, setDismissed] = useState(false);
const allowCheck = user?.is_admin && !updatesDisabled;
const updateCheck = useCallback(() => { const updateCheck = useCallback(() => {
setItem(VERSION_CHECK, { version: latest, time: Date.now() }); setItem(VERSION_CHECK, { version: latest, time: Date.now() });
@ -27,12 +32,12 @@ export default function UpdateNotice() {
} }
useEffect(() => { useEffect(() => {
if (!checked) { if (!checked && allowCheck) {
checkVersion(); checkVersion();
} }
}, []); }, [checked]);
if (!hasUpdate || dismissed) { if (!hasUpdate || dismissed || !allowCheck) {
return null; return null;
} }

View File

@ -1,4 +1,5 @@
import React from 'react'; import { useRouter } from 'next/router';
import Script from 'next/script';
import classNames from 'classnames'; import classNames from 'classnames';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import Link from 'components/common/Link'; import Link from 'components/common/Link';
@ -8,6 +9,7 @@ import { HOMEPAGE_URL, REPO_URL } from 'lib/constants';
export default function Footer() { export default function Footer() {
const { current } = useStore(); const { current } = useStore();
const { pathname } = useRouter();
return ( return (
<footer className={classNames(styles.footer, 'row')}> <footer className={classNames(styles.footer, 'row')}>
@ -28,9 +30,7 @@ export default function Footer() {
<div className={classNames(styles.version, 'col-12 col-md-4')}> <div className={classNames(styles.version, 'col-12 col-md-4')}>
<Link href={REPO_URL}>{`v${current}`}</Link> <Link href={REPO_URL}>{`v${current}`}</Link>
</div> </div>
{!process.env.telemetryDisabled && ( {!pathname.includes('/share/') && <Script src={`/telemetry.js?v=${current}`} />}
<img src={`https://i.umami.is/a.png?v=${current}`} alt="" />
)}
</footer> </footer>
); );
} }

View File

@ -8,10 +8,10 @@ import ThemeButton from 'components/settings/ThemeButton';
import HamburgerButton from 'components/common/HamburgerButton'; import HamburgerButton from 'components/common/HamburgerButton';
import UpdateNotice from 'components/common/UpdateNotice'; import UpdateNotice from 'components/common/UpdateNotice';
import UserButton from 'components/settings/UserButton'; import UserButton from 'components/settings/UserButton';
import Logo from 'assets/logo.svg';
import styles from './Header.module.css';
import useUser from 'hooks/useUser'; import useUser from 'hooks/useUser';
import { HOMEPAGE_URL } from 'lib/constants'; import { HOMEPAGE_URL } from 'lib/constants';
import Logo from 'assets/logo.svg';
import styles from './Header.module.css';
export default function Header() { export default function Header() {
const { user } = useUser(); const { user } = useUser();
@ -19,7 +19,7 @@ export default function Header() {
return ( return (
<> <>
{user?.is_admin && !process.env.updatesDisabled && <UpdateNotice />} <UpdateNotice />
<header className={classNames(styles.header, 'row')}> <header className={classNames(styles.header, 'row')}>
<div className={styles.title}> <div className={styles.title}>
<Icon icon={<Logo />} size="large" className={styles.logo} /> <Icon icon={<Logo />} size="large" className={styles.logo} />

View File

@ -12,7 +12,7 @@ export default function EventsChart({ websiteId, className, token }) {
const [{ startDate, endDate, unit, modified }] = useDateRange(websiteId); const [{ startDate, endDate, unit, modified }] = useDateRange(websiteId);
const [timezone] = useTimezone(); const [timezone] = useTimezone();
const { const {
query: { url, eventType }, query: { url, eventName },
} = usePageQuery(); } = usePageQuery();
const { data, loading } = useFetch( const { data, loading } = useFetch(
@ -24,11 +24,11 @@ export default function EventsChart({ websiteId, className, token }) {
unit, unit,
tz: timezone, tz: timezone,
url, url,
event_type: eventType, event_name: eventName,
token, token,
}, },
}, },
[modified, eventType], [modified, eventName],
); );
const datasets = useMemo(() => { const datasets = useMemo(() => {

View File

@ -92,8 +92,7 @@ export default function RealtimeLog({ data, websites, websiteId }) {
} }
function getDetail({ function getDetail({
event_type, event_name,
event_value,
view_id, view_id,
session_id, session_id,
url, url,
@ -103,10 +102,10 @@ export default function RealtimeLog({ data, websites, websiteId }) {
device, device,
website_id, website_id,
}) { }) {
if (event_type) { if (event_name) {
return ( return (
<div> <div>
<Tag>{event_type}</Tag> {event_value} <Tag>{event_name}</Tag>
</div> </div>
); );
} }

View File

@ -1,5 +1,5 @@
import { useState } from 'react'; import { useState } from 'react';
import { FormattedMessage } from 'react-intl'; import { defineMessages, useIntl } from 'react-intl';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import Page from 'components/layout/Page'; import Page from 'components/layout/Page';
import PageHeader from 'components/layout/PageHeader'; import PageHeader from 'components/layout/PageHeader';
@ -7,19 +7,24 @@ import WebsiteList from 'components/pages/WebsiteList';
import Button from 'components/common/Button'; import Button from 'components/common/Button';
import DashboardSettingsButton from 'components/settings/DashboardSettingsButton'; import DashboardSettingsButton from 'components/settings/DashboardSettingsButton';
import useFetch from 'hooks/useFetch'; import useFetch from 'hooks/useFetch';
import useStore from 'store/app'; import useDashboard from 'store/dashboard';
import DashboardEdit from './DashboardEdit';
import styles from './WebsiteList.module.css'; import styles from './WebsiteList.module.css';
const selector = state => state.dashboard; const messages = defineMessages({
dashboard: { id: 'label.dashboard', defaultMessage: 'Dashboard' },
more: { id: 'label.more', defaultMessage: 'More' },
});
export default function Dashboard() { export default function Dashboard() {
const router = useRouter(); const router = useRouter();
const { id } = router.query; const { id } = router.query;
const userId = id?.[0]; const userId = id?.[0];
const store = useStore(selector); const dashboard = useDashboard();
const { showCharts, limit } = store; const { showCharts, limit, editing } = dashboard;
const [max, setMax] = useState(limit); const [max, setMax] = useState(limit);
const { data } = useFetch('/websites', { params: { user_id: userId } }); const { data } = useFetch('/websites', { params: { user_id: userId } });
const { formatMessage } = useIntl();
function handleMore() { function handleMore() {
setMax(max + limit); setMax(max + limit);
@ -32,15 +37,14 @@ export default function Dashboard() {
return ( return (
<Page> <Page>
<PageHeader> <PageHeader>
<div> <div>{formatMessage(messages.dashboard)}</div>
<FormattedMessage id="label.dashboard" defaultMessage="Dashboard" /> {!editing && <DashboardSettingsButton />}
</div>
<DashboardSettingsButton />
</PageHeader> </PageHeader>
<WebsiteList websites={data} showCharts={showCharts} limit={max} /> {editing && <DashboardEdit data={data} />}
{!editing && <WebsiteList data={data} showCharts={showCharts} limit={max} />}
{max < data.length && ( {max < data.length && (
<Button className={styles.button} onClick={handleMore}> <Button className={styles.button} onClick={handleMore}>
<FormattedMessage id="label.more" defaultMessage="More" /> {formatMessage(messages.more)}
</Button> </Button>
)} )}
</Page> </Page>

View File

@ -0,0 +1,104 @@
import { useState } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { DragDropContext, Draggable, Droppable } from 'react-beautiful-dnd';
import useDashboard, { saveDashboard } from 'store/dashboard';
import Button from 'components/common/Button';
import { useMemo } from 'react';
import { orderByWebsiteMap } from 'lib/format';
import styles from './DashboardEdit.module.css';
import classNames from 'classnames';
const messages = defineMessages({
save: { id: 'label.save', defaultMessage: 'Save' },
reset: { id: 'label.reset', defaultMessage: 'Reset' },
cancel: { id: 'label.cancel', defaultMessage: 'Cancel' },
});
const dragId = 'dashboard-website-ordering';
export default function DashboardEdit({ data: websites }) {
const settings = useDashboard();
const { websiteOrder } = settings;
const { formatMessage } = useIntl();
const [order, setOrder] = useState(websiteOrder);
const ordered = useMemo(() => orderByWebsiteMap(websites, order), [websites, order]);
console.log({ order, ordered });
function handleWebsiteDrag({ destination, source }) {
if (!destination || destination.index === source.index) return;
const orderedWebsites = [...ordered];
const [removed] = orderedWebsites.splice(source.index, 1);
orderedWebsites.splice(destination.index, 0, removed);
setOrder(
orderedWebsites.map((i, k) => ({ [i.website_uuid]: k })).reduce((a, b) => ({ ...a, ...b })),
);
}
function handleSave() {
saveDashboard({
editing: false,
websiteOrder: order,
});
}
function handleCancel() {
saveDashboard({ editing: false, websiteOrder });
}
function handleReset() {
setOrder({});
}
return (
<>
<div className={styles.buttons}>
<Button onClick={handleSave} variant="action" size="small">
{formatMessage(messages.save)}
</Button>
<Button onClick={handleCancel} size="small">
{formatMessage(messages.cancel)}
</Button>
<Button onClick={handleReset} size="small">
{formatMessage(messages.reset)}
</Button>
</div>
<div className={styles.dragActive}>
<DragDropContext onDragEnd={handleWebsiteDrag}>
<Droppable droppableId={dragId}>
{(provided, snapshot) => (
<div
{...provided.droppableProps}
ref={provided.innerRef}
style={{ marginBottom: snapshot.isDraggingOver ? 260 : null }}
>
{ordered.map(({ website_id, name, domain }, index) => (
<Draggable key={website_id} draggableId={`${dragId}-${website_id}`} index={index}>
{(provided, snapshot) => (
<div
ref={provided.innerRef}
className={classNames(styles.item, {
[styles.active]: snapshot.isDragging,
})}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
<div className={styles.text}>
<h1>{name}</h1>
<h2>{domain}</h2>
</div>
</div>
)}
</Draggable>
))}
</div>
)}
</Droppable>
</DragDropContext>
</div>
</>
);
}

View File

@ -0,0 +1,40 @@
.buttons {
display: flex;
justify-content: flex-end;
gap: 10px;
margin-bottom: 20px;
}
.item {
padding: 5px 0;
}
.item h1 {
font-weight: 600;
font-size: 16px;
}
.item h2 {
font-size: 14px;
color: var(--gray700);
}
.text {
padding: 20px;
border-radius: 5px;
border: 1px solid var(--gray400);
background: var(--gray50);
}
.active .text {
border-color: var(--gray600);
box-shadow: 4px 4px 4px var(--gray100);
}
.dragActive {
cursor: grab;
}
.dragActive:active {
cursor: grabbing;
}

View File

@ -37,7 +37,8 @@ export default function TestConsole() {
function handleClick() { function handleClick() {
window.umami('event (default)'); window.umami('event (default)');
window.umami.trackView('/page-view', 'https://www.google.com'); window.umami.trackView('/page-view', 'https://www.google.com');
window.umami.trackEvent('event (custom)', 'custom-type'); window.umami.trackEvent('event (custom)', null, 'custom-type');
window.umami.trackEvent('event (custom)', { test: 'test-data' }, 'custom-data-type');
} }
return ( return (

View File

@ -1,25 +1,37 @@
import { FormattedMessage } from 'react-intl'; import { defineMessages, useIntl } from 'react-intl';
import Link from 'components/common/Link'; import Link from 'components/common/Link';
import WebsiteChart from 'components/metrics/WebsiteChart'; import WebsiteChart from 'components/metrics/WebsiteChart';
import Page from 'components/layout/Page'; import Page from 'components/layout/Page';
import EmptyPlaceholder from 'components/common/EmptyPlaceholder'; import EmptyPlaceholder from 'components/common/EmptyPlaceholder';
import Arrow from 'assets/arrow-right.svg'; import Arrow from 'assets/arrow-right.svg';
import styles from './WebsiteList.module.css'; import styles from './WebsiteList.module.css';
import useDashboard from 'store/dashboard';
import { orderByWebsiteMap } from 'lib/format';
import { useMemo } from 'react';
const messages = defineMessages({
noWebsites: {
id: 'message.no-websites-configured',
defaultMessage: "You don't have any websites configured.",
},
goToSettngs: {
id: 'message.go-to-settings',
defaultMessage: 'Go to settings',
},
});
export default function WebsiteList({ data, showCharts, limit }) {
const { websiteOrder } = useDashboard();
const { formatMessage } = useIntl();
const websites = useMemo(() => orderByWebsiteMap(data, websiteOrder), [data, websiteOrder]);
export default function WebsiteList({ websites, showCharts, limit }) {
if (websites.length === 0) { if (websites.length === 0) {
return ( return (
<Page> <Page>
<EmptyPlaceholder <EmptyPlaceholder msg={formatMessage(messages.noWebsites)}>
msg={
<FormattedMessage
id="message.no-websites-configured"
defaultMessage="You don't have any websites configured."
/>
}
>
<Link href="/settings" icon={<Arrow />} iconRight> <Link href="/settings" icon={<Arrow />} iconRight>
<FormattedMessage id="message.go-to-settings" defaultMessage="Go to settings" /> {formatMessage(messages.goToSettngs)}
</Link> </Link>
</EmptyPlaceholder> </EmptyPlaceholder>
</Page> </Page>

View File

@ -1,26 +1,39 @@
import React from 'react'; import React from 'react';
import { FormattedMessage } from 'react-intl'; import { defineMessages, useIntl } from 'react-intl';
import MenuButton from 'components/common/MenuButton'; import MenuButton from 'components/common/MenuButton';
import Gear from 'assets/gear.svg'; import Gear from 'assets/gear.svg';
import useStore, { setDashboard, defaultDashboardConfig } from 'store/app'; import { saveDashboard } from 'store/dashboard';
const selector = state => state.dashboard; const messages = defineMessages({
toggleCharts: { id: 'message.toggle-charts', defaultMessage: 'Toggle charts' },
editDashboard: { id: 'message.edit-dashboard', defaultMessage: 'Edit dashboard' },
});
export default function DashboardSettingsButton() { export default function DashboardSettingsButton() {
const settings = useStore(selector); const { formatMessage } = useIntl();
const menuOptions = [ const menuOptions = [
{ {
label: <FormattedMessage id="message.toggle-charts" defaultMessage="Toggle charts" />, label: formatMessage(messages.toggleCharts),
value: 'charts', value: 'charts',
}, },
{
label: formatMessage(messages.editDashboard),
value: 'order',
},
]; ];
function handleSelect(value) { function handleSelect(value) {
if (value === 'charts') { if (value === 'charts') {
setDashboard({ ...defaultDashboardConfig, showCharts: !settings.showCharts }); saveDashboard(state => {
const bs = { showCharts: !state.showCharts };
console.log('WTF', { state, bs });
return bs;
});
}
if (value === 'order') {
saveDashboard({ editing: true });
} }
//setDashboard(value);
} }
return <MenuButton icon={<Gear />} options={menuOptions} onSelect={handleSelect} hideLabel />; return <MenuButton icon={<Gear />} options={menuOptions} onSelect={handleSelect} hideLabel />;

View File

@ -0,0 +1,5 @@
.buttonGroup {
display: flex;
place-items: center;
gap: 10px;
}

View File

@ -36,6 +36,7 @@ export default function WebsiteSettings() {
const [showUrl, setShowUrl] = useState(); const [showUrl, setShowUrl] = useState();
const [saved, setSaved] = useState(0); const [saved, setSaved] = useState(0);
const [message, setMessage] = useState(); const [message, setMessage] = useState();
const { data } = useFetch('/websites', { params: { include_all: !!user?.is_admin } }, [saved]); const { data } = useFetch('/websites', { params: { include_all: !!user?.is_admin } }, [saved]);
const Buttons = row => ( const Buttons = row => (

View File

@ -0,0 +1,62 @@
-- DropForeignKey
ALTER TABLE `event` DROP FOREIGN KEY `event_ibfk_1`;
ALTER TABLE `event` DROP FOREIGN KEY `event_ibfk_2`;
DROP INDEX `event_created_at_idx` ON `event`;
DROP INDEX `event_session_id_idx` ON `event`;
DROP INDEX `event_website_id_idx` ON `event`;
CREATE INDEX `event_old_created_at_idx` ON `event` (created_at);
CREATE INDEX `event_old_session_id_idx` ON `event` (session_id);
CREATE INDEX `event_old_website_id_idx` ON `event` (website_id);
-- RenameTable
RENAME TABLE `event` TO `_event_old`;
-- CreateTable
CREATE TABLE `event`
(
`event_id` INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
`website_id` INTEGER UNSIGNED NOT NULL,
`session_id` INTEGER UNSIGNED NOT NULL,
`created_at` TIMESTAMP(0) NULL DEFAULT CURRENT_TIMESTAMP(0),
`url` VARCHAR(500) NOT NULL,
`event_name` VARCHAR(50) NOT NULL,
INDEX `event_created_at_idx`(`created_at`),
INDEX `event_session_id_idx`(`session_id`),
INDEX `event_website_id_idx`(`website_id`),
PRIMARY KEY (`event_id`)
) ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- AddForeignKey
ALTER TABLE `event` ADD CONSTRAINT `event_ibfk_2` FOREIGN KEY (`session_id`) REFERENCES `session`(`session_id`) ON DELETE CASCADE ON UPDATE NO ACTION;
-- AddForeignKey
ALTER TABLE `event` ADD CONSTRAINT `event_ibfk_1` FOREIGN KEY (`website_id`) REFERENCES `website`(`website_id`) ON DELETE CASCADE ON UPDATE NO ACTION;
-- CreateTable
CREATE TABLE `event_data` (
`event_data_id` INTEGER NOT NULL AUTO_INCREMENT,
`event_id` INTEGER UNSIGNED NOT NULL,
`event_data` JSON NOT NULL,
UNIQUE INDEX `event_data_event_id_key`(`event_id`),
PRIMARY KEY (`event_data_id`)
) ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- AddForeignKey
ALTER TABLE `event_data` ADD CONSTRAINT `event_data_event_id_fkey` FOREIGN KEY (`event_id`) REFERENCES `event`(`event_id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- RenameIndex
ALTER TABLE `account` RENAME INDEX `username` TO `account_username_key`;
-- RenameIndex
ALTER TABLE `session` RENAME INDEX `session_uuid` TO `session_session_uuid_key`;
-- RenameIndex
ALTER TABLE `website` RENAME INDEX `share_id` TO `website_share_id_key`;
-- RenameIndex
ALTER TABLE `website` RENAME INDEX `website_uuid` TO `website_website_uuid_key`;

View File

@ -9,7 +9,7 @@ datasource db {
model account { model account {
user_id Int @id @default(autoincrement()) @db.UnsignedInt user_id Int @id @default(autoincrement()) @db.UnsignedInt
username String @unique(map: "username") @db.VarChar(255) username String @unique() @db.VarChar(255)
password String @db.VarChar(60) password String @db.VarChar(60)
is_admin Boolean @default(false) is_admin Boolean @default(false)
created_at DateTime? @default(now()) @db.Timestamp(0) created_at DateTime? @default(now()) @db.Timestamp(0)
@ -18,21 +18,28 @@ model account {
} }
model event { model event {
event_id Int @id @default(autoincrement()) @db.UnsignedInt event_id Int @id @default(autoincrement()) @db.UnsignedInt
website_id Int @db.UnsignedInt website_id Int @db.UnsignedInt
session_id Int @db.UnsignedInt session_id Int @db.UnsignedInt
created_at DateTime? @default(now()) @db.Timestamp(0) created_at DateTime? @default(now()) @db.Timestamp(0)
url String @db.VarChar(500) url String @db.VarChar(500)
event_type String @db.VarChar(50) event_name String @db.VarChar(50)
event_value String @db.VarChar(50) session session @relation(fields: [session_id], references: [session_id], onDelete: Cascade, onUpdate: NoAction, map: "event_ibfk_2")
session session @relation(fields: [session_id], references: [session_id], onDelete: Cascade, onUpdate: NoAction, map: "event_ibfk_2") website website @relation(fields: [website_id], references: [website_id], onDelete: Cascade, onUpdate: NoAction, map: "event_ibfk_1")
website website @relation(fields: [website_id], references: [website_id], onDelete: Cascade, onUpdate: NoAction, map: "event_ibfk_1") event_data event_data?
@@index([created_at]) @@index([created_at])
@@index([session_id]) @@index([session_id])
@@index([website_id]) @@index([website_id])
} }
model event_data {
event_data_id Int @id @default(autoincrement())
event_id Int @unique @db.UnsignedInt
event_data Json
event event @relation(fields: [event_id], references: [event_id])
}
model pageview { model pageview {
view_id Int @id @default(autoincrement()) @db.UnsignedInt view_id Int @id @default(autoincrement()) @db.UnsignedInt
website_id Int @db.UnsignedInt website_id Int @db.UnsignedInt
@ -52,7 +59,7 @@ model pageview {
model session { model session {
session_id Int @id @default(autoincrement()) @db.UnsignedInt session_id Int @id @default(autoincrement()) @db.UnsignedInt
session_uuid String @unique(map: "session_uuid") @db.VarChar(36) session_uuid String @unique() @db.VarChar(36)
website_id Int @db.UnsignedInt website_id Int @db.UnsignedInt
created_at DateTime? @default(now()) @db.Timestamp(0) created_at DateTime? @default(now()) @db.Timestamp(0)
hostname String? @db.VarChar(100) hostname String? @db.VarChar(100)
@ -72,11 +79,11 @@ model session {
model website { model website {
website_id Int @id @default(autoincrement()) @db.UnsignedInt website_id Int @id @default(autoincrement()) @db.UnsignedInt
website_uuid String @unique(map: "website_uuid") @db.VarChar(36) website_uuid String @unique() @db.VarChar(36)
user_id Int @db.UnsignedInt user_id Int @db.UnsignedInt
name String @db.VarChar(100) name String @db.VarChar(100)
domain String? @db.VarChar(500) domain String? @db.VarChar(500)
share_id String? @unique(map: "share_id") @db.VarChar(64) share_id String? @unique() @db.VarChar(64)
created_at DateTime? @default(now()) @db.Timestamp(0) created_at DateTime? @default(now()) @db.Timestamp(0)
account account @relation(fields: [user_id], references: [user_id], onDelete: Cascade, onUpdate: NoAction, map: "website_ibfk_1") account account @relation(fields: [user_id], references: [user_id], onDelete: Cascade, onUpdate: NoAction, map: "website_ibfk_1")
event event[] event event[]

View File

@ -0,0 +1,66 @@
-- DropForeignKey
ALTER TABLE "event" DROP CONSTRAINT "event_session_id_fkey";
ALTER TABLE "event" DROP CONSTRAINT "event_website_id_fkey";
-- RenameIndex
ALTER INDEX "event_pkey" RENAME TO "event_old_pkey";
ALTER INDEX "event_created_at_idx" RENAME TO "event_old_created_at_idx";
ALTER INDEX "event_session_id_idx" RENAME TO "event_old_session_id_idx";
ALTER INDEX "event_website_id_idx" RENAME TO "event_old_website_id_idx";
-- RenameTable
ALTER TABLE "event" RENAME TO "_event_old";
-- CreateTable
CREATE TABLE "event" (
"event_id" SERIAL NOT NULL,
"website_id" INTEGER NOT NULL,
"session_id" INTEGER NOT NULL,
"created_at" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,
"url" VARCHAR(500) NOT NULL,
"event_name" VARCHAR(50) NOT NULL,
PRIMARY KEY ("event_id")
);
-- CreateIndex
CREATE INDEX "event_created_at_idx" ON "event"("created_at");
-- CreateIndex
CREATE INDEX "event_session_id_idx" ON "event"("session_id");
-- CreateIndex
CREATE INDEX "event_website_id_idx" ON "event"("website_id");
-- AddForeignKey
ALTER TABLE "event" ADD CONSTRAINT "event_session_id_fkey" FOREIGN KEY ("session_id") REFERENCES "session"("session_id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "event" ADD CONSTRAINT "event_website_id_fkey" FOREIGN KEY ("website_id") REFERENCES "website"("website_id") ON DELETE CASCADE ON UPDATE CASCADE;
-- CreateTable
CREATE TABLE "event_data" (
"event_data_id" SERIAL NOT NULL,
"event_id" INTEGER NOT NULL,
"event_data" JSONB NOT NULL,
CONSTRAINT "event_data_pkey" PRIMARY KEY ("event_data_id")
);
-- CreateIndex
CREATE UNIQUE INDEX "event_data_event_id_key" ON "event_data"("event_id");
-- AddForeignKey
ALTER TABLE "event_data" ADD CONSTRAINT "event_data_event_id_fkey" FOREIGN KEY ("event_id") REFERENCES "event"("event_id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- RenameIndex
ALTER INDEX "account.username_unique" RENAME TO "account_username_key";
-- RenameIndex
ALTER INDEX "session.session_uuid_unique" RENAME TO "session_session_uuid_key";
-- RenameIndex
ALTER INDEX "website.share_id_unique" RENAME TO "website_share_id_key";
-- RenameIndex
ALTER INDEX "website.website_uuid_unique" RENAME TO "website_website_uuid_key";

View File

@ -18,21 +18,28 @@ model account {
} }
model event { model event {
event_id Int @id @default(autoincrement()) event_id Int @id @default(autoincrement())
website_id Int website_id Int
session_id Int session_id Int
created_at DateTime? @default(now()) @db.Timestamptz(6) created_at DateTime? @default(now()) @db.Timestamptz(6)
url String @db.VarChar(500) url String @db.VarChar(500)
event_type String @db.VarChar(50) event_name String @db.VarChar(50)
event_value String @db.VarChar(50) session session @relation(fields: [session_id], references: [session_id], onDelete: Cascade)
session session @relation(fields: [session_id], references: [session_id], onDelete: Cascade, onUpdate: NoAction) website website @relation(fields: [website_id], references: [website_id], onDelete: Cascade)
website website @relation(fields: [website_id], references: [website_id], onDelete: Cascade, onUpdate: NoAction) event_data event_data?
@@index([created_at]) @@index([created_at])
@@index([session_id]) @@index([session_id])
@@index([website_id]) @@index([website_id])
} }
model event_data {
event_data_id Int @id @default(autoincrement())
event_id Int @unique
event_data Json
event event @relation(fields: [event_id], references: [event_id])
}
model pageview { model pageview {
view_id Int @id @default(autoincrement()) view_id Int @id @default(autoincrement())
website_id Int website_id Int
@ -40,8 +47,8 @@ model pageview {
created_at DateTime? @default(now()) @db.Timestamptz(6) created_at DateTime? @default(now()) @db.Timestamptz(6)
url String @db.VarChar(500) url String @db.VarChar(500)
referrer String? @db.VarChar(500) referrer String? @db.VarChar(500)
session session @relation(fields: [session_id], references: [session_id], onDelete: Cascade, onUpdate: NoAction) session session @relation(fields: [session_id], references: [session_id], onDelete: Cascade)
website website @relation(fields: [website_id], references: [website_id], onDelete: Cascade, onUpdate: NoAction) website website @relation(fields: [website_id], references: [website_id], onDelete: Cascade)
@@index([created_at]) @@index([created_at])
@@index([session_id]) @@index([session_id])
@ -58,13 +65,13 @@ model session {
hostname String? @db.VarChar(100) hostname String? @db.VarChar(100)
browser String? @db.VarChar(20) browser String? @db.VarChar(20)
os String? @db.VarChar(20) os String? @db.VarChar(20)
device String? @db.VarChar(20)
screen String? @db.VarChar(11) screen String? @db.VarChar(11)
language String? @db.VarChar(35) language String? @db.VarChar(35)
country String? @db.Char(2) country String? @db.Char(2)
device String? @db.VarChar(20) website website @relation(fields: [website_id], references: [website_id], onDelete: Cascade)
website website @relation(fields: [website_id], references: [website_id], onDelete: Cascade, onUpdate: NoAction)
event event[]
pageview pageview[] pageview pageview[]
event event[]
@@index([created_at]) @@index([created_at])
@@index([website_id]) @@index([website_id])
@ -73,15 +80,15 @@ model session {
model website { model website {
website_id Int @id @default(autoincrement()) website_id Int @id @default(autoincrement())
website_uuid String @unique @db.Uuid website_uuid String @unique @db.Uuid
name String @db.VarChar(100)
created_at DateTime? @default(now()) @db.Timestamptz(6)
user_id Int user_id Int
name String @db.VarChar(100)
domain String? @db.VarChar(500) domain String? @db.VarChar(500)
share_id String? @unique(map: "website_share_id_idx") @db.VarChar(64) share_id String? @unique @db.VarChar(64)
account account @relation(fields: [user_id], references: [user_id], onDelete: NoAction, onUpdate: NoAction) created_at DateTime? @default(now()) @db.Timestamptz(6)
event event[] account account @relation(fields: [user_id], references: [user_id], onDelete: Cascade)
pageview pageview[] pageview pageview[]
session session[] session session[]
event event[]
@@index([user_id]) @@index([user_id])
} }

View File

@ -6,7 +6,7 @@ import useStore from 'store/app';
const selector = state => state.shareToken; const selector = state => state.shareToken;
function parseHeaders(headers = {}, { authToken, shareToken }) { function parseHeaders(headers, { authToken, shareToken }) {
if (authToken) { if (authToken) {
headers.authorization = `Bearer ${authToken}`; headers.authorization = `Bearer ${authToken}`;
} }
@ -25,7 +25,7 @@ export default function useApi() {
return { return {
get: useCallback( get: useCallback(
async (url, params, headers) => { async (url, params = {}, headers = {}) => {
return get( return get(
`${basePath}/api${url}`, `${basePath}/api${url}`,
params, params,
@ -36,7 +36,7 @@ export default function useApi() {
), ),
post: useCallback( post: useCallback(
async (url, params, headers) => { async (url, params = {}, headers = {}) => {
return post( return post(
`${basePath}/api${url}`, `${basePath}/api${url}`,
params, params,
@ -47,7 +47,7 @@ export default function useApi() {
), ),
put: useCallback( put: useCallback(
async (url, params, headers) => { async (url, params = {}, headers = {}) => {
return put( return put(
`${basePath}/api${url}`, `${basePath}/api${url}`,
params, params,
@ -58,7 +58,7 @@ export default function useApi() {
), ),
del: useCallback( del: useCallback(
async (url, params, headers) => { async (url, params = {}, headers = {}) => {
return del( return del(
`${basePath}/api${url}`, `${basePath}/api${url}`,
params, params,

24
hooks/useConfig.js Normal file
View File

@ -0,0 +1,24 @@
import { useEffect } from 'react';
import useStore, { setConfig } from 'store/app';
import useApi from 'hooks/useApi';
let fetched = false;
export default function useConfig() {
const { config } = useStore();
const { get } = useApi();
async function loadConfig() {
const { data } = await get('/config');
setConfig(data);
}
useEffect(() => {
if (!config && !fetched) {
fetched = true;
loadConfig();
}
}, []);
return config || {};
}

View File

@ -8,7 +8,9 @@
"metrics.device.desktop", "metrics.device.desktop",
"metrics.device.laptop", "metrics.device.laptop",
"metrics.device.tablet", "metrics.device.tablet",
"metrics.referrers" "metrics.referrers",
"metrics.utm",
"metrics.utm_medium"
], ],
"en-GB": "*", "en-GB": "*",
"fr-FR": ["metrics.actions", "metrics.pages"], "fr-FR": ["metrics.actions", "metrics.pages"],

View File

@ -68,6 +68,7 @@
"message.confirm-reset": "هل أنت متأكد من اعادة تعيين الإحصائيات لـ {target}؟", "message.confirm-reset": "هل أنت متأكد من اعادة تعيين الإحصائيات لـ {target}؟",
"message.copied": "تم النسخ!", "message.copied": "تم النسخ!",
"message.delete-warning": "كافة البيانات المرتبطة سيم حذفها ايضا.", "message.delete-warning": "كافة البيانات المرتبطة سيم حذفها ايضا.",
"message.edit-dashboard": "Edit dashboard",
"message.failure": "حدث خطأ ما.", "message.failure": "حدث خطأ ما.",
"message.get-share-url": "احصل على رابط المشاركة", "message.get-share-url": "احصل على رابط المشاركة",
"message.get-tracking-code": "احصل على كود التتبع", "message.get-tracking-code": "احصل على كود التتبع",

View File

@ -68,6 +68,7 @@
"message.confirm-reset": "আপনি কি নিশ্চিত যে আপনি {target} এর পরিসংখ্যান পুনরায় সেট করতে চান?", "message.confirm-reset": "আপনি কি নিশ্চিত যে আপনি {target} এর পরিসংখ্যান পুনরায় সেট করতে চান?",
"message.copied": "কপি হয়েছে", "message.copied": "কপি হয়েছে",
"message.delete-warning": "সমস্ত সম্পর্কিত ডেটা পাশাপাশি মুছে ফেলা হবে।", "message.delete-warning": "সমস্ত সম্পর্কিত ডেটা পাশাপাশি মুছে ফেলা হবে।",
"message.edit-dashboard": "Edit dashboard",
"message.failure": "কিছু ভুল হয়েছে।", "message.failure": "কিছু ভুল হয়েছে।",
"message.get-share-url": "শেয়ার ইউআরএল", "message.get-share-url": "শেয়ার ইউআরএল",
"message.get-tracking-code": "ট্র্যাকিং কোড পান", "message.get-tracking-code": "ট্র্যাকিং কোড পান",

View File

@ -68,6 +68,7 @@
"message.confirm-reset": "Segur que vols restablir les estadístiques de {target}?", "message.confirm-reset": "Segur que vols restablir les estadístiques de {target}?",
"message.copied": "S'ha copiat", "message.copied": "S'ha copiat",
"message.delete-warning": "També s'esborraran totes les dades relacionades.", "message.delete-warning": "També s'esborraran totes les dades relacionades.",
"message.edit-dashboard": "Edit dashboard",
"message.failure": "S'ha produït un error.", "message.failure": "S'ha produït un error.",
"message.get-share-url": "Obté l'enllaç per compartir", "message.get-share-url": "Obté l'enllaç per compartir",
"message.get-tracking-code": "Obté el codi de seguiment", "message.get-tracking-code": "Obté el codi de seguiment",

View File

@ -68,6 +68,7 @@
"message.confirm-reset": "Are your sure you want to reset {target}'s statistics?", "message.confirm-reset": "Are your sure you want to reset {target}'s statistics?",
"message.copied": "Zkopírováno!", "message.copied": "Zkopírováno!",
"message.delete-warning": "Všechna související data budou také smazána.", "message.delete-warning": "Všechna související data budou také smazána.",
"message.edit-dashboard": "Edit dashboard",
"message.failure": "Něco se pokazilo.", "message.failure": "Něco se pokazilo.",
"message.get-share-url": "Získat sdílené URL", "message.get-share-url": "Získat sdílené URL",
"message.get-tracking-code": "Získat měřící kód", "message.get-tracking-code": "Získat měřící kód",

View File

@ -68,6 +68,7 @@
"message.confirm-reset": "Er du sikker på at du ville nulstille {target}'s statistikker?", "message.confirm-reset": "Er du sikker på at du ville nulstille {target}'s statistikker?",
"message.copied": "Kopieret!", "message.copied": "Kopieret!",
"message.delete-warning": "Alle tilknyttede data slettes også.", "message.delete-warning": "Alle tilknyttede data slettes også.",
"message.edit-dashboard": "Edit dashboard",
"message.failure": "Noget gik galt.", "message.failure": "Noget gik galt.",
"message.get-share-url": "Få delings-URL", "message.get-share-url": "Få delings-URL",
"message.get-tracking-code": "Få sporingskode", "message.get-tracking-code": "Få sporingskode",

View File

@ -37,7 +37,7 @@
"label.more": "Mehr", "label.more": "Mehr",
"label.name": "Name", "label.name": "Name",
"label.new-password": "Neues Passwort", "label.new-password": "Neues Passwort",
"label.none": "None", "label.none": "Keine",
"label.owner": "Besitzer", "label.owner": "Besitzer",
"label.password": "Passwort", "label.password": "Passwort",
"label.passwords-dont-match": "Passwörter stimmen nicht überein", "label.passwords-dont-match": "Passwörter stimmen nicht überein",
@ -68,6 +68,7 @@
"message.confirm-reset": "Sind Sie sicher, dass Sie die Statistiken von {target} zurücksetzen wollen?", "message.confirm-reset": "Sind Sie sicher, dass Sie die Statistiken von {target} zurücksetzen wollen?",
"message.copied": "In Zwischenablage kopiert!", "message.copied": "In Zwischenablage kopiert!",
"message.delete-warning": "Alle zugehörigen Daten werden ebenfalls gelöscht.", "message.delete-warning": "Alle zugehörigen Daten werden ebenfalls gelöscht.",
"message.edit-dashboard": "Edit dashboard",
"message.failure": "Es ist ein Fehler aufgetreten.", "message.failure": "Es ist ein Fehler aufgetreten.",
"message.get-share-url": "Freigabe-URL abrufen", "message.get-share-url": "Freigabe-URL abrufen",
"message.get-tracking-code": "Erstelle Tracking Kennung", "message.get-tracking-code": "Erstelle Tracking Kennung",
@ -107,11 +108,11 @@
"metrics.screens": "Bildschirmauflösungen", "metrics.screens": "Bildschirmauflösungen",
"metrics.unique-visitors": "Eindeutige Besucher", "metrics.unique-visitors": "Eindeutige Besucher",
"metrics.utm": "UTM", "metrics.utm": "UTM",
"metrics.utm_campaign": "UTM Campaign", "metrics.utm_campaign": "UTM Kampagne",
"metrics.utm_content": "UTM Content", "metrics.utm_content": "UTM Inhalt",
"metrics.utm_medium": "UTM Medium", "metrics.utm_medium": "UTM Medium",
"metrics.utm_source": "UTM Source", "metrics.utm_source": "UTM Quelle",
"metrics.utm_term": "UTM Term", "metrics.utm_term": "UTM Begriff",
"metrics.views": "Aufrufe", "metrics.views": "Aufrufe",
"metrics.visitors": "Besucher" "metrics.visitors": "Besucher"
} }

View File

@ -68,6 +68,7 @@
"message.confirm-reset": "Are your sure you want to reset {target}'s statistics?", "message.confirm-reset": "Are your sure you want to reset {target}'s statistics?",
"message.copied": "Αντιγράφηκε!", "message.copied": "Αντιγράφηκε!",
"message.delete-warning": "Όλα τα σχετικά δεδομένα θα διαγραφούν επίσης.", "message.delete-warning": "Όλα τα σχετικά δεδομένα θα διαγραφούν επίσης.",
"message.edit-dashboard": "Edit dashboard",
"message.failure": "Κάτι πήγε στραβά.", "message.failure": "Κάτι πήγε στραβά.",
"message.get-share-url": "Λήψη URL κοινής χρήσης", "message.get-share-url": "Λήψη URL κοινής χρήσης",
"message.get-tracking-code": "Λήψη κώδικα παρακολούθησης", "message.get-tracking-code": "Λήψη κώδικα παρακολούθησης",

View File

@ -68,6 +68,7 @@
"message.confirm-reset": "Are you sure you want to reset {target}'s statistics?", "message.confirm-reset": "Are you sure you want to reset {target}'s statistics?",
"message.copied": "Copied!", "message.copied": "Copied!",
"message.delete-warning": "All associated data will be deleted as well.", "message.delete-warning": "All associated data will be deleted as well.",
"message.edit-dashboard": "Edit dashboard",
"message.failure": "Something went wrong.", "message.failure": "Something went wrong.",
"message.get-share-url": "Get share URL", "message.get-share-url": "Get share URL",
"message.get-tracking-code": "Get tracking code", "message.get-tracking-code": "Get tracking code",

View File

@ -68,6 +68,7 @@
"message.confirm-reset": "Are you sure you want to reset {target}'s statistics?", "message.confirm-reset": "Are you sure you want to reset {target}'s statistics?",
"message.copied": "Copied!", "message.copied": "Copied!",
"message.delete-warning": "All associated data will be deleted as well.", "message.delete-warning": "All associated data will be deleted as well.",
"message.edit-dashboard": "Edit dashboard",
"message.failure": "Something went wrong.", "message.failure": "Something went wrong.",
"message.get-share-url": "Get share URL", "message.get-share-url": "Get share URL",
"message.get-tracking-code": "Get tracking code", "message.get-tracking-code": "Get tracking code",

View File

@ -68,6 +68,7 @@
"message.confirm-reset": "¿Seguro que deseas restablecer las estadísticas de {target}?", "message.confirm-reset": "¿Seguro que deseas restablecer las estadísticas de {target}?",
"message.copied": "¡Copiado!", "message.copied": "¡Copiado!",
"message.delete-warning": "Toda la información relacionada será eliminada.", "message.delete-warning": "Toda la información relacionada será eliminada.",
"message.edit-dashboard": "Edit dashboard",
"message.failure": "Algo falló.", "message.failure": "Algo falló.",
"message.get-share-url": "Obtener URL para compartir", "message.get-share-url": "Obtener URL para compartir",
"message.get-tracking-code": "Obtener código de rastreo", "message.get-tracking-code": "Obtener código de rastreo",

View File

@ -68,6 +68,7 @@
"message.confirm-reset": "آیا از بازنشانی آمار {target} مطمئن هستید?", "message.confirm-reset": "آیا از بازنشانی آمار {target} مطمئن هستید?",
"message.copied": "کپی شد!", "message.copied": "کپی شد!",
"message.delete-warning": "همه‌ی داده‌های مرتبط هم حذف خواهد شد.", "message.delete-warning": "همه‌ی داده‌های مرتبط هم حذف خواهد شد.",
"message.edit-dashboard": "Edit dashboard",
"message.failure": "مشکلی پیش آمده است.", "message.failure": "مشکلی پیش آمده است.",
"message.get-share-url": "دریافت URL برای اشتراک گذاری", "message.get-share-url": "دریافت URL برای اشتراک گذاری",
"message.get-tracking-code": "گرفتن کد رهگیری", "message.get-tracking-code": "گرفتن کد رهگیری",

View File

@ -68,6 +68,7 @@
"message.confirm-reset": "Haluatko varmasti poistaa sivuston {target} tilastot?", "message.confirm-reset": "Haluatko varmasti poistaa sivuston {target} tilastot?",
"message.copied": "Kopioitu!", "message.copied": "Kopioitu!",
"message.delete-warning": "Kaikki siihen liittyvät tiedot poistetaan.", "message.delete-warning": "Kaikki siihen liittyvät tiedot poistetaan.",
"message.edit-dashboard": "Edit dashboard",
"message.failure": "Jotain meni pieleen.", "message.failure": "Jotain meni pieleen.",
"message.get-share-url": "Hanki jakamisen URL-osoite", "message.get-share-url": "Hanki jakamisen URL-osoite",
"message.get-tracking-code": "Hanki seurantakoodi", "message.get-tracking-code": "Hanki seurantakoodi",

View File

@ -68,6 +68,7 @@
"message.confirm-reset": "Are your sure you want to reset {target}'s statistics?", "message.confirm-reset": "Are your sure you want to reset {target}'s statistics?",
"message.copied": "Avrita!", "message.copied": "Avrita!",
"message.delete-warning": "Øll data ið er knýtt at verður eisini strika.", "message.delete-warning": "Øll data ið er knýtt at verður eisini strika.",
"message.edit-dashboard": "Edit dashboard",
"message.failure": "Okkurt bleiv gali.", "message.failure": "Okkurt bleiv gali.",
"message.get-share-url": "Fá leinku sum tú kanst deila", "message.get-share-url": "Fá leinku sum tú kanst deila",
"message.get-tracking-code": "Fá sporings kotu", "message.get-tracking-code": "Fá sporings kotu",

View File

@ -68,6 +68,7 @@
"message.confirm-reset": "Êtes-vous sûr de vouloir réinistialiser les statistiques de {target} ?", "message.confirm-reset": "Êtes-vous sûr de vouloir réinistialiser les statistiques de {target} ?",
"message.copied": "Copié !", "message.copied": "Copié !",
"message.delete-warning": "Toutes les données associées seront également supprimées.", "message.delete-warning": "Toutes les données associées seront également supprimées.",
"message.edit-dashboard": "Edit dashboard",
"message.failure": "Un problème est survenu.", "message.failure": "Un problème est survenu.",
"message.get-share-url": "Obtenir l'URL de partage", "message.get-share-url": "Obtenir l'URL de partage",
"message.get-tracking-code": "Obtenir le code de suivi", "message.get-tracking-code": "Obtenir le code de suivi",

View File

@ -68,6 +68,7 @@
"message.confirm-reset": "Tes a certeza de querer restablecer as estatísticas de {target}?", "message.confirm-reset": "Tes a certeza de querer restablecer as estatísticas de {target}?",
"message.copied": "Copiado!", "message.copied": "Copiado!",
"message.delete-warning": "Tamén serán borrados tódolos datos asociados.", "message.delete-warning": "Tamén serán borrados tódolos datos asociados.",
"message.edit-dashboard": "Edit dashboard",
"message.failure": "Houbo un fallo.", "message.failure": "Houbo un fallo.",
"message.get-share-url": "Obter URL de compartición", "message.get-share-url": "Obter URL de compartición",
"message.get-tracking-code": "Obter código de seguimento", "message.get-tracking-code": "Obter código de seguimento",

View File

@ -68,6 +68,7 @@
"message.confirm-reset": "Are your sure you want to reset {target}'s statistics?", "message.confirm-reset": "Are your sure you want to reset {target}'s statistics?",
"message.copied": "הועתק!", "message.copied": "הועתק!",
"message.delete-warning": "כל המידע המקושר יימחק", "message.delete-warning": "כל המידע המקושר יימחק",
"message.edit-dashboard": "Edit dashboard",
"message.failure": "משהו השתבש", "message.failure": "משהו השתבש",
"message.get-share-url": "קבלת URL שיתוף", "message.get-share-url": "קבלת URL שיתוף",
"message.get-tracking-code": "קבלת קוד מעקב", "message.get-tracking-code": "קבלת קוד מעקב",

View File

@ -68,6 +68,7 @@
"message.confirm-reset": "Are your sure you want to reset {target}'s statistics?", "message.confirm-reset": "Are your sure you want to reset {target}'s statistics?",
"message.copied": "कॉपी हो गया!", "message.copied": "कॉपी हो गया!",
"message.delete-warning": "सभी संबद्ध डेटा को भी हटा दिया जाएगा।", "message.delete-warning": "सभी संबद्ध डेटा को भी हटा दिया जाएगा।",
"message.edit-dashboard": "Edit dashboard",
"message.failure": "कुछ गलत हो गया।", "message.failure": "कुछ गलत हो गया।",
"message.get-share-url": "शेयर URL प्राप्त करें", "message.get-share-url": "शेयर URL प्राप्त करें",
"message.get-tracking-code": "ट्रैकिंग कोड प्राप्त करें", "message.get-tracking-code": "ट्रैकिंग कोड प्राप्त करें",

View File

@ -68,6 +68,7 @@
"message.confirm-reset": "Are your sure you want to reset {target}'s statistics?", "message.confirm-reset": "Are your sure you want to reset {target}'s statistics?",
"message.copied": "Kimásolva!", "message.copied": "Kimásolva!",
"message.delete-warning": "Minden társított adat törlésre kerül.", "message.delete-warning": "Minden társított adat törlésre kerül.",
"message.edit-dashboard": "Edit dashboard",
"message.failure": "Valami baj történt.", "message.failure": "Valami baj történt.",
"message.get-share-url": "Megosztási URL kimásolása", "message.get-share-url": "Megosztási URL kimásolása",
"message.get-tracking-code": "Követési kód kimásolása", "message.get-tracking-code": "Követési kód kimásolása",

View File

@ -68,6 +68,7 @@
"message.confirm-reset": "Anda yakin ingin mengatur ulang statistik {target}?", "message.confirm-reset": "Anda yakin ingin mengatur ulang statistik {target}?",
"message.copied": "Tersalin!", "message.copied": "Tersalin!",
"message.delete-warning": "Semua data terkait juga akan dihapus.", "message.delete-warning": "Semua data terkait juga akan dihapus.",
"message.edit-dashboard": "Edit dashboard",
"message.failure": "Ada yang salah.", "message.failure": "Ada yang salah.",
"message.get-share-url": "Dapatkan URL berbagi", "message.get-share-url": "Dapatkan URL berbagi",
"message.get-tracking-code": "Dapatkan kode pelacakan", "message.get-tracking-code": "Dapatkan kode pelacakan",

View File

@ -68,6 +68,7 @@
"message.confirm-reset": "Sei sicuro di voler azzerare le statistiche di {target}?", "message.confirm-reset": "Sei sicuro di voler azzerare le statistiche di {target}?",
"message.copied": "Copiato!", "message.copied": "Copiato!",
"message.delete-warning": "Saranno eliminati anche tutti i dati associati.", "message.delete-warning": "Saranno eliminati anche tutti i dati associati.",
"message.edit-dashboard": "Edit dashboard",
"message.failure": "Si è verificato un errore.", "message.failure": "Si è verificato un errore.",
"message.get-share-url": "Ottieni l'URL di condivisione", "message.get-share-url": "Ottieni l'URL di condivisione",
"message.get-tracking-code": "Ottieni il codice di tracking", "message.get-tracking-code": "Ottieni il codice di tracking",

View File

@ -68,6 +68,7 @@
"message.confirm-reset": "Are your sure you want to reset {target}'s statistics?", "message.confirm-reset": "Are your sure you want to reset {target}'s statistics?",
"message.copied": "コピーしました!", "message.copied": "コピーしました!",
"message.delete-warning": "関連するすべてのデータも削除されます。", "message.delete-warning": "関連するすべてのデータも削除されます。",
"message.edit-dashboard": "Edit dashboard",
"message.failure": "問題が発生しました。", "message.failure": "問題が発生しました。",
"message.get-share-url": "共有リンクを取得", "message.get-share-url": "共有リンクを取得",
"message.get-tracking-code": "トラッキングコードを取得", "message.get-tracking-code": "トラッキングコードを取得",

View File

@ -68,6 +68,7 @@
"message.confirm-reset": "Are your sure you want to reset {target}'s statistics?", "message.confirm-reset": "Are your sure you want to reset {target}'s statistics?",
"message.copied": "복사했습니다!", "message.copied": "복사했습니다!",
"message.delete-warning": "관련된 모든 데이터도 삭제됩니다.", "message.delete-warning": "관련된 모든 데이터도 삭제됩니다.",
"message.edit-dashboard": "Edit dashboard",
"message.failure": "오류가 발생하였습니다.", "message.failure": "오류가 발생하였습니다.",
"message.get-share-url": "공유 URL 가져오기", "message.get-share-url": "공유 URL 가져오기",
"message.get-tracking-code": "추적 코드 가져오기", "message.get-tracking-code": "추적 코드 가져오기",

View File

@ -68,6 +68,7 @@
"message.confirm-reset": "Are esate tikri, jog norite atstatyti svetainės {target} statistikos duomenis?", "message.confirm-reset": "Are esate tikri, jog norite atstatyti svetainės {target} statistikos duomenis?",
"message.copied": "Nukopijuota!", "message.copied": "Nukopijuota!",
"message.delete-warning": "Visi susiję duomenys taip pat bus ištrinti.", "message.delete-warning": "Visi susiję duomenys taip pat bus ištrinti.",
"message.edit-dashboard": "Edit dashboard",
"message.failure": "Kažkas įvyko ne taip.", "message.failure": "Kažkas įvyko ne taip.",
"message.get-share-url": "Gauti bendrinimo nuorodą", "message.get-share-url": "Gauti bendrinimo nuorodą",
"message.get-tracking-code": "Gauti sekimo kodą", "message.get-tracking-code": "Gauti sekimo kodą",

View File

@ -68,6 +68,7 @@
"message.confirm-reset": "Are your sure you want to reset {target}'s statistics?", "message.confirm-reset": "Are your sure you want to reset {target}'s statistics?",
"message.copied": "Хуулсан!", "message.copied": "Хуулсан!",
"message.delete-warning": "Үүнтэй холбоотой бүх өгөгдөл устах болно.", "message.delete-warning": "Үүнтэй холбоотой бүх өгөгдөл устах болно.",
"message.edit-dashboard": "Edit dashboard",
"message.failure": "Ямар нэг зүйл буруу боллоо.", "message.failure": "Ямар нэг зүйл буруу боллоо.",
"message.get-share-url": "Хуваалцах холбоос авах", "message.get-share-url": "Хуваалцах холбоос авах",
"message.get-tracking-code": "Мөрдөх код авах", "message.get-tracking-code": "Мөрдөх код авах",

View File

@ -68,6 +68,7 @@
"message.confirm-reset": "Are your sure you want to reset {target}'s statistics?", "message.confirm-reset": "Are your sure you want to reset {target}'s statistics?",
"message.copied": "Disalin!", "message.copied": "Disalin!",
"message.delete-warning": "Semua data yang berkaitan juga akan dihapuskan.", "message.delete-warning": "Semua data yang berkaitan juga akan dihapuskan.",
"message.edit-dashboard": "Edit dashboard",
"message.failure": "Ada yang tidak kena.", "message.failure": "Ada yang tidak kena.",
"message.get-share-url": "Dapatkan URL berkongsi", "message.get-share-url": "Dapatkan URL berkongsi",
"message.get-tracking-code": "Dapatkan kod penjejakan", "message.get-tracking-code": "Dapatkan kod penjejakan",

View File

@ -68,6 +68,7 @@
"message.confirm-reset": "Er du sikker på at du vil nullstille {target}'s statistikk?", "message.confirm-reset": "Er du sikker på at du vil nullstille {target}'s statistikk?",
"message.copied": "Kopiert!", "message.copied": "Kopiert!",
"message.delete-warning": "Alle tilknyttede data slettes også.", "message.delete-warning": "Alle tilknyttede data slettes også.",
"message.edit-dashboard": "Edit dashboard",
"message.failure": "Noe gikk galt.", "message.failure": "Noe gikk galt.",
"message.get-share-url": "Få delings-URL", "message.get-share-url": "Få delings-URL",
"message.get-tracking-code": "Få sporingskode", "message.get-tracking-code": "Få sporingskode",

View File

@ -68,6 +68,7 @@
"message.confirm-reset": "Weet je zeker dat je de statistieken van {target} opnieuw wilt instellen?", "message.confirm-reset": "Weet je zeker dat je de statistieken van {target} opnieuw wilt instellen?",
"message.copied": "Gekopiëerd!", "message.copied": "Gekopiëerd!",
"message.delete-warning": "Alle verwante gegezens zullen ook verwijderd worden.", "message.delete-warning": "Alle verwante gegezens zullen ook verwijderd worden.",
"message.edit-dashboard": "Edit dashboard",
"message.failure": "Er is iets misgegaan.", "message.failure": "Er is iets misgegaan.",
"message.get-share-url": "Openbare URL", "message.get-share-url": "Openbare URL",
"message.get-tracking-code": "Tracking code", "message.get-tracking-code": "Tracking code",

View File

@ -68,6 +68,7 @@
"message.confirm-reset": "Czy na pewno chcesz zresetować statystyki {target}?", "message.confirm-reset": "Czy na pewno chcesz zresetować statystyki {target}?",
"message.copied": "Skopiowano!", "message.copied": "Skopiowano!",
"message.delete-warning": "Wszystkie powiązane dane również zostaną usunięte.", "message.delete-warning": "Wszystkie powiązane dane również zostaną usunięte.",
"message.edit-dashboard": "Edit dashboard",
"message.failure": "Coś poszło nie tak.", "message.failure": "Coś poszło nie tak.",
"message.get-share-url": "Uzyskaj adres URL udostępniania", "message.get-share-url": "Uzyskaj adres URL udostępniania",
"message.get-tracking-code": "Pobierz kod śledzenia", "message.get-tracking-code": "Pobierz kod śledzenia",

View File

@ -68,6 +68,7 @@
"message.confirm-reset": "Você tem certeza que deseja redefinir as estatísticas de {target}?", "message.confirm-reset": "Você tem certeza que deseja redefinir as estatísticas de {target}?",
"message.copied": "Copiado!", "message.copied": "Copiado!",
"message.delete-warning": "Todos os dados associados também serão eliminados.", "message.delete-warning": "Todos os dados associados também serão eliminados.",
"message.edit-dashboard": "Edit dashboard",
"message.failure": "Ocorreu um erro.", "message.failure": "Ocorreu um erro.",
"message.get-share-url": "Obter link de compartilhamento", "message.get-share-url": "Obter link de compartilhamento",
"message.get-tracking-code": "Obter código de rastreamento", "message.get-tracking-code": "Obter código de rastreamento",

View File

@ -68,6 +68,7 @@
"message.confirm-reset": "Tem a certeza que pretende restaurar as estatísticas de {target}?", "message.confirm-reset": "Tem a certeza que pretende restaurar as estatísticas de {target}?",
"message.copied": "Copiado!", "message.copied": "Copiado!",
"message.delete-warning": "Todos os dados associados também serão eliminados.", "message.delete-warning": "Todos os dados associados também serão eliminados.",
"message.edit-dashboard": "Edit dashboard",
"message.failure": "Ocorreu um erro.", "message.failure": "Ocorreu um erro.",
"message.get-share-url": "Obter link de partilha", "message.get-share-url": "Obter link de partilha",
"message.get-tracking-code": "Obter código de rastreamento", "message.get-tracking-code": "Obter código de rastreamento",

View File

@ -68,6 +68,7 @@
"message.confirm-reset": "Sunteți sigur că doriți să resetați statisticile pentru {target}?", "message.confirm-reset": "Sunteți sigur că doriți să resetați statisticile pentru {target}?",
"message.copied": "Copiat!", "message.copied": "Copiat!",
"message.delete-warning": "Toate datele asociate vor fi șterse, de asemenea.", "message.delete-warning": "Toate datele asociate vor fi șterse, de asemenea.",
"message.edit-dashboard": "Edit dashboard",
"message.failure": "Ceva n-a mers bine.", "message.failure": "Ceva n-a mers bine.",
"message.get-share-url": "Obține adresa URL de partajare", "message.get-share-url": "Obține adresa URL de partajare",
"message.get-tracking-code": "Obține codul de urmărire", "message.get-tracking-code": "Obține codul de urmărire",

View File

@ -68,6 +68,7 @@
"message.confirm-reset": "Вы уверены, что хотите сбросить статистику {target}?", "message.confirm-reset": "Вы уверены, что хотите сбросить статистику {target}?",
"message.copied": "Скопировано!", "message.copied": "Скопировано!",
"message.delete-warning": "Все связанные данные будут также удалены.", "message.delete-warning": "Все связанные данные будут также удалены.",
"message.edit-dashboard": "Edit dashboard",
"message.failure": "Что-то пошло не так.", "message.failure": "Что-то пошло не так.",
"message.get-share-url": "Получить публичную ссылку", "message.get-share-url": "Получить публичную ссылку",
"message.get-tracking-code": "Получить код отслеживания", "message.get-tracking-code": "Получить код отслеживания",

View File

@ -68,6 +68,7 @@
"message.confirm-reset": "Are your sure you want to reset {target}'s statistics?", "message.confirm-reset": "Are your sure you want to reset {target}'s statistics?",
"message.copied": "Skopírované!", "message.copied": "Skopírované!",
"message.delete-warning": "Všetky príbuzné data budu tiež zmazané.", "message.delete-warning": "Všetky príbuzné data budu tiež zmazané.",
"message.edit-dashboard": "Edit dashboard",
"message.failure": "Niečo sa pokazilo.", "message.failure": "Niečo sa pokazilo.",
"message.get-share-url": "Získať zdielané URL", "message.get-share-url": "Získať zdielané URL",
"message.get-tracking-code": "Získať tracking kód", "message.get-tracking-code": "Získať tracking kód",

View File

@ -68,6 +68,7 @@
"message.confirm-reset": "Are your sure you want to reset {target}'s statistics?", "message.confirm-reset": "Are your sure you want to reset {target}'s statistics?",
"message.copied": "Kopirano!", "message.copied": "Kopirano!",
"message.delete-warning": "Izbrisani bodo tudi vsi povezani podatki.", "message.delete-warning": "Izbrisani bodo tudi vsi povezani podatki.",
"message.edit-dashboard": "Edit dashboard",
"message.failure": "Prišlo je do napake.", "message.failure": "Prišlo je do napake.",
"message.get-share-url": "Pridobi URL za skupno rabo", "message.get-share-url": "Pridobi URL za skupno rabo",
"message.get-tracking-code": "Pridobi kodo za sledenje", "message.get-tracking-code": "Pridobi kodo za sledenje",

View File

@ -68,6 +68,7 @@
"message.confirm-reset": "Är du säker på att du vill återställa statistiken för {target}?", "message.confirm-reset": "Är du säker på att du vill återställa statistiken för {target}?",
"message.copied": "Kopierad!", "message.copied": "Kopierad!",
"message.delete-warning": "All tillhörande data kommer också raderas.", "message.delete-warning": "All tillhörande data kommer också raderas.",
"message.edit-dashboard": "Edit dashboard",
"message.failure": "Något gick fel.", "message.failure": "Något gick fel.",
"message.get-share-url": "Visa delnings-URL", "message.get-share-url": "Visa delnings-URL",
"message.get-tracking-code": "Visa spårningskod", "message.get-tracking-code": "Visa spårningskod",

View File

@ -68,6 +68,7 @@
"message.confirm-reset": "Are your sure you want to reset {target}'s statistics?", "message.confirm-reset": "Are your sure you want to reset {target}'s statistics?",
"message.copied": "நகலெடுக்கப்பட்டது!", "message.copied": "நகலெடுக்கப்பட்டது!",
"message.delete-warning": "தொடர்புடைய எல்லா தரவும் நீக்கப்படும்.", "message.delete-warning": "தொடர்புடைய எல்லா தரவும் நீக்கப்படும்.",
"message.edit-dashboard": "Edit dashboard",
"message.failure": "ஏதோ தவறு நடந்துவிட்டது.", "message.failure": "ஏதோ தவறு நடந்துவிட்டது.",
"message.get-share-url": "கள முகவரியை ஐப் பெறுக", "message.get-share-url": "கள முகவரியை ஐப் பெறுக",
"message.get-tracking-code": "கண்காணிப்பு குறியீட்டைப் பெறுக", "message.get-tracking-code": "கண்காணிப்பு குறியீட்டைப் பெறுக",

118
lang/th-TH.json Normal file
View File

@ -0,0 +1,118 @@
{
"label.accounts": "บัญชี",
"label.add-account": "เพิ่มบัญชี",
"label.add-website": "เพิ่มเว็บไซต์",
"label.administrator": "ผู้ดูแลระบบ",
"label.all": "ทั้งหมด",
"label.all-events": "เหตุการณ์ทั้งหมด",
"label.all-time": "ทุกช่วงเวลา",
"label.all-websites": "เว็บไซต์ทั้งหมด",
"label.back": "ย้อนกลับ",
"label.cancel": "ยกเลิก",
"label.change-password": "เปลี่ยนรหัสผ่าน",
"label.confirm-password": "ยืนยันรหัสผ่าน",
"label.copy-to-clipboard": "คัดลอกไปยังคลิปบอร์ด",
"label.current-password": "รหัสผ่านปัจจุบัน",
"label.custom-range": "กำหนดช่วงเวลา",
"label.dashboard": "แดชบอร์ด",
"label.date-range": "ตั้งแต่วันที่",
"label.default-date-range": "ช่วงเวลา",
"label.delete": "ลบ",
"label.delete-account": "ลบบัญชี",
"label.delete-website": "ลบเว็บไซต์",
"label.dismiss": "ยกเลิก",
"label.domain": "โดเมน",
"label.edit": "แก้ไข",
"label.edit-account": "แก้ไขบัญชี",
"label.edit-website": "แก้ไขเว็บไซต์",
"label.enable-share-url": "เปิดใช้งานการแชร์ลิงก์",
"label.invalid": "ไม่ถูกต้อง",
"label.invalid-domain": "โดเมนไม่ถูกต้อง",
"label.language": "ภาษา",
"label.last-days": "{x} วันที่ผ่านมา",
"label.last-hours": "{x} ชั่วโมงที่ผ่านมา",
"label.logged-in-as": "เข้าสู่ระบบโดย {username}",
"label.login": "เข้าสู่ระบบ",
"label.logout": "ออกจากระบบ",
"label.more": "เพิ่มเติม",
"label.name": "ชื่อ",
"label.new-password": "รหัสผ่านใหม่",
"label.none": "ไม่ได้กำหนด",
"label.owner": "เจ้าของ",
"label.password": "รหัสผ่าน",
"label.passwords-dont-match": "รหัสผ่านไม่ตรงกัน",
"label.profile": "โปรไฟล์",
"label.realtime": "เรียลไทม์",
"label.realtime-logs": "Log แบบเรียลไทม์",
"label.refresh": "รีเฟรช",
"label.required": "ต้องการ",
"label.reset": "รีเซต",
"label.reset-website": "รีเซตข้อมูลสถิติ",
"label.save": "บันทึก",
"label.settings": "ตั้งค่า",
"label.share-url": "แชร์ลิงก์",
"label.single-day": "วันที่",
"label.theme": "ธีม",
"label.this-month": "เดือนปัจจุบัน",
"label.this-week": "สัปดาห์ปัจจุบัน",
"label.this-year": "ปีปัจจุบัน",
"label.timezone": "เขตเวลา",
"label.today": "วันนี้",
"label.tracking-code": "โค้ดสำหรับใช้ติดตาม",
"label.unknown": "ไม่รู้จัก",
"label.username": "ชื่อผู้ใช้",
"label.view-details": "แสดงรายละเอียด",
"label.websites": "เว็บไซต์",
"message.active-users": "มีผู้ใช้งาน {x} {x, plural, one {คนในขณะนี้} other {คนในขณะนี้}}",
"message.confirm-delete": "คุณแน่ใจหรือไม่ว่าต้องการลบ {target} ?",
"message.confirm-reset": "คุณแน่ใจหรือไม่ว่าต้องการรีเซตข้อมูลสถิติของ {target} ?",
"message.copied": "คัดลอกแล้ว!",
"message.delete-warning": "ข้อมูลที่เกี่ยวข้องทั้งหมดจะถูกลบ.",
"message.edit-dashboard": "Edit dashboard",
"message.failure": "เกิดข้อผิดพลาด.",
"message.get-share-url": "รับลิงก์สำหรับแชร์",
"message.get-tracking-code": "รับโค้ดสำหรับใช้ติดตาม",
"message.go-to-settings": "ไปที่การตั้งค่า",
"message.incorrect-username-password": "ชื่อผู้ใช้หรือรหัสผ่านไม่ถูกต้อง.",
"message.log.visitor": "ผู้เข้าชมจาก {country} กำลังใช้งานผ่าน {browser} บน {os} {device}",
"message.new-version-available": "umami เวอร์ชันใหม่ ({version}) มาแล้ว!",
"message.no-data-available": "ไม่มีข้อมูล.",
"message.no-websites-configured": "คุณยังไม่ได้ตั้งค่าเว็บไซต์ใด ๆ ไว้.",
"message.page-not-found": "ไม่พบหน้านี้.",
"message.powered-by": "ขับเคลื่อนโดย {name}",
"message.reset-warning": "สถิติทั้งหมดสำหรับเว็บไซต์นี้จะถูกลบออก แต่โค้ดสำหรับใช้ติดตามของคุณจะยังคงอยู่เหมือนเดิม.",
"message.save-success": "บันทึกข้อมูลเรียบร้อย.",
"message.share-url": "นี่คือลิงก์ที่แชร์แบบสาธารณะสำหรับ {target}.",
"message.toggle-charts": "เปิด/ปิดแผนภูมิ",
"message.track-stats": "หากต้องการติดตามสถิติสำหรับ {target} ให้วางโค้ดต่อไปนี้ในส่วน {head} ของเว็บไซต์ของคุณ.",
"message.type-delete": "พิมพ์ข้อความ {delete} ในช่องด้านล่างเพื่อยืนยัน.",
"message.type-reset": "พิมพ์ข้อความ {reset} ในช่องด้านล่างเพื่อยืนยัน.",
"metrics.actions": "การกระทำ",
"metrics.average-visit-time": "ระยะเวลาเข้าชมเฉลี่ย",
"metrics.bounce-rate": "อัตราตีกลับ",
"metrics.browsers": "เบราว์เซอร์",
"metrics.countries": "ประเทศ",
"metrics.device.desktop": "เดสก์ท็อป",
"metrics.device.laptop": "แล็ปท็อป",
"metrics.device.mobile": "โทรศัพท์มือถือ",
"metrics.device.tablet": "แท็บเล็ต",
"metrics.devices": "อุปกรณ์",
"metrics.events": "เหตุการณ์",
"metrics.filter.combined": "ข้อมูลรวม",
"metrics.filter.raw": "ข้อมูลดิบ",
"metrics.languages": "ภาษา",
"metrics.operating-systems": "ระบบปฏิบัติการ",
"metrics.page-views": "การเข้าชม",
"metrics.pages": "หน้าเพจ",
"metrics.referrers": "แหล่งที่มา",
"metrics.screens": "ขนาดหน้าจอ",
"metrics.unique-visitors": "ผู้เข้าชม",
"metrics.utm": "UTM",
"metrics.utm_campaign": "UTM Campaign",
"metrics.utm_content": "UTM Content",
"metrics.utm_medium": "UTM Medium",
"metrics.utm_source": "UTM Source",
"metrics.utm_term": "UTM Term",
"metrics.views": "การเข้าชม",
"metrics.visitors": "ผู้เข้าชม"
}

View File

@ -68,6 +68,7 @@
"message.confirm-reset": "Are your sure you want to reset {target}'s statistics?", "message.confirm-reset": "Are your sure you want to reset {target}'s statistics?",
"message.copied": "Panoya kopyalandı!", "message.copied": "Panoya kopyalandı!",
"message.delete-warning": "İlişkili tüm veriler de silinecektir.", "message.delete-warning": "İlişkili tüm veriler de silinecektir.",
"message.edit-dashboard": "Edit dashboard",
"message.failure": "Bir şeyler ters gitti!", "message.failure": "Bir şeyler ters gitti!",
"message.get-share-url": "Paylaşım adresini al", "message.get-share-url": "Paylaşım adresini al",
"message.get-tracking-code": "İzleme kodunu al", "message.get-tracking-code": "İzleme kodunu al",

View File

@ -68,6 +68,7 @@
"message.confirm-reset": "Ви впевнені, що бажаєте скинути статистику для {target}?", "message.confirm-reset": "Ви впевнені, що бажаєте скинути статистику для {target}?",
"message.copied": "Скопійовано!", "message.copied": "Скопійовано!",
"message.delete-warning": "Усі пов'язані дані будуть видалені також.", "message.delete-warning": "Усі пов'язані дані будуть видалені також.",
"message.edit-dashboard": "Edit dashboard",
"message.failure": "Щось пішло не так.", "message.failure": "Щось пішло не так.",
"message.get-share-url": "Отримати публічне посилання", "message.get-share-url": "Отримати публічне посилання",
"message.get-tracking-code": "Отримати код для відслідковування", "message.get-tracking-code": "Отримати код для відслідковування",

View File

@ -68,6 +68,7 @@
"message.confirm-reset": "کیا آپ واقعی {target} کے اعدادوشمار کو دوبارہ ترتیب دینا چاہتے ہیں؟", "message.confirm-reset": "کیا آپ واقعی {target} کے اعدادوشمار کو دوبارہ ترتیب دینا چاہتے ہیں؟",
"message.copied": "کاپی کیا گیا!", "message.copied": "کاپی کیا گیا!",
"message.delete-warning": "تمام متعلقہ ڈیٹا بھی حذف کر دیا جائے گا۔", "message.delete-warning": "تمام متعلقہ ڈیٹا بھی حذف کر دیا جائے گا۔",
"message.edit-dashboard": "Edit dashboard",
"message.failure": "کچھ غلط ہو گیا.", "message.failure": "کچھ غلط ہو گیا.",
"message.get-share-url": "شیئر URL حاصل کریں", "message.get-share-url": "شیئر URL حاصل کریں",
"message.get-tracking-code": "ٹریکنگ کوڈ حاصل کریں", "message.get-tracking-code": "ٹریکنگ کوڈ حاصل کریں",

View File

@ -68,6 +68,7 @@
"message.confirm-reset": "Bạn có chắc chắn muốn tái thiết lập thống kê {target}?", "message.confirm-reset": "Bạn có chắc chắn muốn tái thiết lập thống kê {target}?",
"message.copied": "Đã sao chép!", "message.copied": "Đã sao chép!",
"message.delete-warning": "Tất cả các dữ liệu liên quan cũng sẽ bị xoá.", "message.delete-warning": "Tất cả các dữ liệu liên quan cũng sẽ bị xoá.",
"message.edit-dashboard": "Edit dashboard",
"message.failure": "Đã xảy ra lỗi.", "message.failure": "Đã xảy ra lỗi.",
"message.get-share-url": "Lấy URL chia sẻ", "message.get-share-url": "Lấy URL chia sẻ",
"message.get-tracking-code": "Lấy mã theo dõi", "message.get-tracking-code": "Lấy mã theo dõi",

View File

@ -68,6 +68,7 @@
"message.confirm-reset": "您确定要重置 {target} 的数据吗?", "message.confirm-reset": "您确定要重置 {target} 的数据吗?",
"message.copied": "复制成功!", "message.copied": "复制成功!",
"message.delete-warning": "所有相关数据将会被删除。", "message.delete-warning": "所有相关数据将会被删除。",
"message.edit-dashboard": "Edit dashboard",
"message.failure": "出现错误。", "message.failure": "出现错误。",
"message.get-share-url": "获取共享链接", "message.get-share-url": "获取共享链接",
"message.get-tracking-code": "获取跟踪代码", "message.get-tracking-code": "获取跟踪代码",

View File

@ -68,6 +68,7 @@
"message.confirm-reset": "您確定要重置 {target} 的數據嗎?", "message.confirm-reset": "您確定要重置 {target} 的數據嗎?",
"message.copied": "複製成功!", "message.copied": "複製成功!",
"message.delete-warning": "所有相關數據將會被刪除。", "message.delete-warning": "所有相關數據將會被刪除。",
"message.edit-dashboard": "Edit dashboard",
"message.failure": "出現錯誤。", "message.failure": "出現錯誤。",
"message.get-share-url": "獲得分享連結", "message.get-share-url": "獲得分享連結",
"message.get-tracking-code": "獲得追蹤代碼", "message.get-tracking-code": "獲得追蹤代碼",

View File

@ -9,6 +9,7 @@ export const SHARE_TOKEN_HEADER = 'x-umami-share-token';
export const HOMEPAGE_URL = 'https://umami.is'; export const HOMEPAGE_URL = 'https://umami.is';
export const REPO_URL = 'https://github.com/umami-software/umami'; export const REPO_URL = 'https://github.com/umami-software/umami';
export const UPDATES_URL = 'https://api.umami.is/v1/updates'; export const UPDATES_URL = 'https://api.umami.is/v1/updates';
export const TELEMETRY_PIXEL = 'https://i.umami.is/a.png';
export const DEFAULT_LOCALE = 'en-US'; export const DEFAULT_LOCALE = 'en-US';
export const DEFAULT_THEME = 'light'; export const DEFAULT_THEME = 'light';

View File

@ -217,7 +217,7 @@ export function getFilterQuery(table, column, filters = {}, params = []) {
} }
break; break;
case 'event_type': case 'event_name':
if (table === 'event') { if (table === 'event') {
arr.push(`and ${table}.${key}=$${params.length + 1}`); arr.push(`and ${table}.${key}=$${params.length + 1}`);
params.push(decodeURIComponent(filter)); params.push(decodeURIComponent(filter));
@ -247,17 +247,17 @@ export function getFilterQuery(table, column, filters = {}, params = []) {
} }
export function parseFilters(table, column, filters = {}, params = [], sessionKey = 'session_id') { export function parseFilters(table, column, filters = {}, params = [], sessionKey = 'session_id') {
const { domain, url, event_url, referrer, os, browser, device, country, event_type } = filters; const { domain, url, event_url, referrer, os, browser, device, country, event_name } = filters;
const pageviewFilters = { domain, url, referrer }; const pageviewFilters = { domain, url, referrer };
const sessionFilters = { os, browser, device, country }; const sessionFilters = { os, browser, device, country };
const eventFilters = { url: event_url, event_type }; const eventFilters = { url: event_url, event_name };
return { return {
pageviewFilters, pageviewFilters,
sessionFilters, sessionFilters,
eventFilters, eventFilters,
event: { event_type }, event: { event_name },
joinSession: joinSession:
os || browser || device || country os || browser || device || country
? `inner join session on ${table}.${sessionKey} = session.${sessionKey}` ? `inner join session on ${table}.${sessionKey} = session.${sessionKey}`

View File

@ -78,3 +78,14 @@ export function stringToColor(str) {
} }
return color; return color;
} }
export function orderByWebsiteMap(websites, orderMap) {
if (!websites) return [];
let ordered = [...websites];
for (let website of websites) {
ordered[orderMap[website.website_uuid]] = website;
}
return ordered;
}

View File

@ -1,5 +1,6 @@
import { import {
arSA, arSA,
bn,
cs, cs,
sk, sk,
da, da,
@ -30,6 +31,7 @@ import {
sl, sl,
sv, sv,
ta, ta,
th,
tr, tr,
uk, uk,
zhCN, zhCN,
@ -41,6 +43,7 @@ import {
export const languages = { export const languages = {
'ar-SA': { label: 'العربية', dateLocale: arSA, dir: 'rtl' }, 'ar-SA': { label: 'العربية', dateLocale: arSA, dir: 'rtl' },
'bn-BD': { label: 'বাংলা', dateLocale: bn },
'zh-CN': { label: '中文', dateLocale: zhCN }, 'zh-CN': { label: '中文', dateLocale: zhCN },
'zh-TW': { label: '中文(繁體)', dateLocale: zhTW }, 'zh-TW': { label: '中文(繁體)', dateLocale: zhTW },
'ca-ES': { label: 'Català', dateLocale: ca }, 'ca-ES': { label: 'Català', dateLocale: ca },
@ -77,6 +80,7 @@ export const languages = {
'fi-FI': { label: 'Suomi', dateLocale: fi }, 'fi-FI': { label: 'Suomi', dateLocale: fi },
'sv-SE': { label: 'Svenska', dateLocale: sv }, 'sv-SE': { label: 'Svenska', dateLocale: sv },
'ta-IN': { label: 'தமிழ்', dateLocale: ta }, 'ta-IN': { label: 'தமிழ்', dateLocale: ta },
'th-TH': { label: 'ภาษาไทย', dateLocale: th },
'tr-TR': { label: 'Türkçe', dateLocale: tr }, 'tr-TR': { label: 'Türkçe', dateLocale: tr },
'uk-UA': { label: 'українська', dateLocale: uk }, 'uk-UA': { label: 'українська', dateLocale: uk },
'ur-PK': { label: 'Urdu (Pakistan)', dateLocale: uk, dir: 'rtl' }, 'ur-PK': { label: 'Urdu (Pakistan)', dateLocale: uk, dir: 'rtl' },

View File

@ -68,7 +68,7 @@ export async function getCountry(req, ip) {
// Database lookup // Database lookup
if (!lookup) { if (!lookup) {
lookup = await maxmind.open(path.resolve('./public/geo/GeoLite2-Country.mmdb')); lookup = await maxmind.open(path.resolve('node_modules/.geo/GeoLite2-Country.mmdb'));
} }
const result = lookup.get(ip); const result = lookup.get(ip);

View File

@ -61,10 +61,17 @@ export const setItem = (key, data, session) => {
} }
}; };
export const getItem = (key, session) => export const getItem = (key, session) => {
typeof window !== 'undefined' if (typeof window !== 'undefined') {
? JSON.parse((session ? sessionStorage : localStorage).getItem(key) || null) const value = (session ? sessionStorage : localStorage).getItem(key);
: null;
if (value !== 'undefined') {
return JSON.parse(value);
}
}
return null;
};
export const removeItem = (key, session) => { export const removeItem = (key, session) => {
if (typeof window !== 'undefined') { if (typeof window !== 'undefined') {

View File

@ -33,14 +33,6 @@ function customScriptName(req) {
} }
} }
function forceSSL(req, res) {
if (process.env.FORCE_SSL && req.nextUrl.protocol === 'http:') {
res.headers.set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
}
return res;
}
export default function middleware(req) { export default function middleware(req) {
const fns = [customCollectEndpoint, customScriptName]; const fns = [customCollectEndpoint, customScriptName];
@ -51,5 +43,5 @@ export default function middleware(req) {
} }
} }
return forceSSL(req, NextResponse.next()); return NextResponse.next();
} }

View File

@ -1,12 +1,40 @@
require('dotenv').config(); require('dotenv').config();
const pkg = require('./package.json'); const pkg = require('./package.json');
const contentSecurityPolicy = `
default-src 'self';
img-src *;
script-src 'self' 'unsafe-eval';
style-src 'self' 'unsafe-inline';
connect-src 'self' api.umami.is;
frame-ancestors 'self';
`;
const headers = [
{
key: 'X-DNS-Prefetch-Control',
value: 'on',
},
{
key: 'X-Frame-Options',
value: 'SAMEORIGIN',
},
{
key: 'Content-Security-Policy',
value: contentSecurityPolicy.replace(/\s{2,}/g, ' ').trim(),
},
];
if (process.env.FORCE_SSL) {
headers.push({
key: 'Strict-Transport-Security',
value: 'max-age=63072000; includeSubDomains; preload',
});
}
module.exports = { module.exports = {
env: { env: {
currentVersion: pkg.version, currentVersion: pkg.version,
loginDisabled: process.env.DISABLE_LOGIN,
updatesDisabled: process.env.DISABLE_UPDATES,
telemetryDisabled: process.env.DISABLE_TELEMETRY,
}, },
basePath: process.env.BASE_PATH, basePath: process.env.BASE_PATH,
output: 'standalone', output: 'standalone',
@ -25,13 +53,16 @@ module.exports = {
async headers() { async headers() {
return [ return [
{ {
source: `/(.*\\.js)`, source: '/:path*',
headers: [ headers,
{ },
key: 'Cache-Control', ];
value: 'public, max-age=2592000', // 30 days },
}, async rewrites() {
], return [
{
source: '/telemetry.js',
destination: '/api/scripts/telemetry',
}, },
]; ];
}, },

View File

@ -20,7 +20,7 @@
"build-app": "next build", "build-app": "next build",
"build-tracker": "rollup -c rollup.tracker.config.js", "build-tracker": "rollup -c rollup.tracker.config.js",
"build-db": "npm-run-all copy-db-files build-db-client", "build-db": "npm-run-all copy-db-files build-db-client",
"build-lang": "npm-run-all format-lang compile-lang", "build-lang": "npm-run-all format-lang compile-lang download-country-names download-language-names",
"build-geo": "node scripts/build-geo.js", "build-geo": "node scripts/build-geo.js",
"build-db-schema": "prisma db pull", "build-db-schema": "prisma db pull",
"build-db-client": "prisma generate", "build-db-client": "prisma generate",
@ -86,6 +86,7 @@
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"prop-types": "^15.7.2", "prop-types": "^15.7.2",
"react": "^17.0.0", "react": "^17.0.0",
"react-beautiful-dnd": "^13.1.0",
"react-dom": "^17.0.0", "react-dom": "^17.0.0",
"react-intl": "^5.24.7", "react-intl": "^5.24.7",
"react-simple-maps": "^2.3.0", "react-simple-maps": "^2.3.0",
@ -128,5 +129,8 @@
"stylelint-config-prettier": "^9.0.3", "stylelint-config-prettier": "^9.0.3",
"stylelint-config-recommended": "^7.0.0", "stylelint-config-recommended": "^7.0.0",
"tar": "^6.1.2" "tar": "^6.1.2"
} },
"cacheDirectories": [
".next/cache"
]
} }

View File

@ -1,4 +1,3 @@
import React from 'react';
import Head from 'next/head'; import Head from 'next/head';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { IntlProvider } from 'react-intl'; import { IntlProvider } from 'react-intl';

View File

@ -65,7 +65,7 @@ export default async (req, res) => {
const { type, payload } = getJsonBody(req); const { type, payload } = getJsonBody(req);
let { url, referrer, event_type, event_value } = payload; let { url, referrer, event_name, event_data } = payload;
if (process.env.REMOVE_TRAILING_SLASH) { if (process.env.REMOVE_TRAILING_SLASH) {
url = removeTrailingSlash(url); url = removeTrailingSlash(url);
@ -74,7 +74,7 @@ export default async (req, res) => {
if (type === 'pageview') { if (type === 'pageview') {
await savePageView(website_id, { session_id, session_uuid, url, referrer }); await savePageView(website_id, { session_id, session_uuid, url, referrer });
} else if (type === 'event') { } else if (type === 'event') {
await saveEvent(website_id, { session_id, session_uuid, url, event_type, event_value }); await saveEvent(website_id, { session_id, session_uuid, url, event_name, event_data });
} else { } else {
return badRequest(res); return badRequest(res);
} }

14
pages/api/config.js Normal file
View File

@ -0,0 +1,14 @@
import { ok, methodNotAllowed } from 'lib/response';
export default async (req, res) => {
if (req.method === 'GET') {
return ok(res, {
basePath: process.env.BASE_PATH || '',
trackerScriptName: process.env.TRACKER_SCRIPT_NAME,
updatesDisabled: !!process.env.DISABLE_UPDATES,
telemetryDisabled: !!process.env.DISABLE_TELEMETRY,
});
}
return methodNotAllowed(res);
};

View File

@ -0,0 +1,18 @@
import { TELEMETRY_PIXEL } from 'lib/constants';
export default function handler(req, res) {
const { v } = req.query;
const script = `
(()=>{const i=document.createElement('img');
i.setAttribute('src','${TELEMETRY_PIXEL}?v=${v}');
i.setAttribute('style','width:0;height:0;position:absolute;pointer-events:none;');
document.body.appendChild(i);})();
`;
res.setHeader('content-type', 'text/javascript');
if (process.env.DISABLE_TELEMETRY) {
res.send('/* telemetry disabled */');
} else {
res.send(script.replace(/\s\s+/g, ''));
}
}

View File

@ -14,7 +14,7 @@ export default async (req, res) => {
return unauthorized(res); return unauthorized(res);
} }
const { id, start_at, end_at, unit, tz, url, event_type } = req.query; const { id, start_at, end_at, unit, tz, url, event_name } = req.query;
if (!moment.tz.zone(tz) || !unitTypes.includes(unit)) { if (!moment.tz.zone(tz) || !unitTypes.includes(unit)) {
return badRequest(res); return badRequest(res);
@ -26,7 +26,7 @@ export default async (req, res) => {
const events = await getEventMetrics(websiteId, startDate, endDate, tz, unit, { const events = await getEventMetrics(websiteId, startDate, endDate, tz, unit, {
url, url,
event_type, event_name,
}); });
return ok(res, events); return ok(res, events);

View File

@ -22,7 +22,7 @@ function getTable(type) {
function getColumn(type) { function getColumn(type) {
if (type === 'event') { if (type === 'event') {
return `concat(event_type, '\t', event_value)`; return `event_name`;
} }
return type; return type;
} }

View File

@ -2,8 +2,8 @@ import React from 'react';
import Layout from 'components/layout/Layout'; import Layout from 'components/layout/Layout';
import LoginForm from 'components/forms/LoginForm'; import LoginForm from 'components/forms/LoginForm';
export default function LoginPage() { export default function LoginPage({ loginDisabled }) {
if (process.env.loginDisabled) { if (loginDisabled) {
return null; return null;
} }
@ -13,3 +13,9 @@ export default function LoginPage() {
</Layout> </Layout>
); );
} }
export async function getServerSideProps() {
return {
props: { loginDisabled: !!process.env.DISABLE_LOGIN },
};
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -477,6 +477,12 @@
"value": "كافة البيانات المرتبطة سيم حذفها ايضا." "value": "كافة البيانات المرتبطة سيم حذفها ايضا."
} }
], ],
"message.edit-dashboard": [
{
"type": 0,
"value": "Edit dashboard"
}
],
"message.failure": [ "message.failure": [
{ {
"type": 0, "type": 0,

View File

@ -0,0 +1,838 @@
{
"label.accounts": [
{
"type": 0,
"value": "অ্যাকাউন্ট"
}
],
"label.add-account": [
{
"type": 0,
"value": "অ্যাকাউন্ট যুক্ত করুন"
}
],
"label.add-website": [
{
"type": 0,
"value": "ওয়েবসাইট যুক্ত করুন"
}
],
"label.administrator": [
{
"type": 0,
"value": "অ্যাডমিন"
}
],
"label.all": [
{
"type": 0,
"value": "সবগুলো"
}
],
"label.all-events": [
{
"type": 0,
"value": "সবগুলো ঘটনা"
}
],
"label.all-time": [
{
"type": 0,
"value": "সব সময়"
}
],
"label.all-websites": [
{
"type": 0,
"value": "সবগুলো ওয়েবসাইট"
}
],
"label.back": [
{
"type": 0,
"value": "পেছনে"
}
],
"label.cancel": [
{
"type": 0,
"value": "বাতিল"
}
],
"label.change-password": [
{
"type": 0,
"value": "পাসওয়ার্ড পরিবর্তন করুন"
}
],
"label.confirm-password": [
{
"type": 0,
"value": "পাসওয়ার্ড নিশ্চিত করুন"
}
],
"label.copy-to-clipboard": [
{
"type": 0,
"value": "কপি করুন"
}
],
"label.current-password": [
{
"type": 0,
"value": "বর্তমান পাসওয়ার্ড"
}
],
"label.custom-range": [
{
"type": 0,
"value": "কাস্টম রেঞ্জ"
}
],
"label.dashboard": [
{
"type": 0,
"value": "ড্যাশবোর্ড"
}
],
"label.date-range": [
{
"type": 0,
"value": "তারিখের পরিসীমা"
}
],
"label.default-date-range": [
{
"type": 0,
"value": "ডিফল্ট তারিখের পরিসীমা"
}
],
"label.delete": [
{
"type": 0,
"value": "মুছে ফেলুন"
}
],
"label.delete-account": [
{
"type": 0,
"value": "অ্যাকাউন্ট মুছুন"
}
],
"label.delete-website": [
{
"type": 0,
"value": "ওয়েবসাইট মুছুন"
}
],
"label.dismiss": [
{
"type": 0,
"value": "বাতিল"
}
],
"label.domain": [
{
"type": 0,
"value": "ডোমেইন"
}
],
"label.edit": [
{
"type": 0,
"value": "সম্পাদনা করুন"
}
],
"label.edit-account": [
{
"type": 0,
"value": "অ্যাকাউন্ট সম্পাদনা করুন"
}
],
"label.edit-website": [
{
"type": 0,
"value": "ওয়েবসাইট সম্পাদনা করুন"
}
],
"label.enable-share-url": [
{
"type": 0,
"value": "শেয়ার ইউআরএল শেয়ার করুন"
}
],
"label.invalid": [
{
"type": 0,
"value": "ভুল"
}
],
"label.invalid-domain": [
{
"type": 0,
"value": "ভুল ডোমেন"
}
],
"label.language": [
{
"type": 0,
"value": "ভাষা"
}
],
"label.last-days": [
{
"type": 0,
"value": "শেষ "
},
{
"type": 1,
"value": "x"
},
{
"type": 0,
"value": " দিন"
}
],
"label.last-hours": [
{
"type": 0,
"value": "শেষ "
},
{
"type": 1,
"value": "x"
},
{
"type": 0,
"value": " ঘন্টা"
}
],
"label.logged-in-as": [
{
"type": 1,
"value": "username"
},
{
"type": 0,
"value": " অ্যাকাউন্টে লগ ইন"
}
],
"label.login": [
{
"type": 0,
"value": "লগিন"
}
],
"label.logout": [
{
"type": 0,
"value": "লগ আউট"
}
],
"label.more": [
{
"type": 0,
"value": "আরও"
}
],
"label.name": [
{
"type": 0,
"value": "নাম"
}
],
"label.new-password": [
{
"type": 0,
"value": "নতুন পাসওয়ার্ড"
}
],
"label.none": [
{
"type": 0,
"value": "কিছুই না"
}
],
"label.owner": [
{
"type": 0,
"value": "মালিক"
}
],
"label.password": [
{
"type": 0,
"value": "পাসওয়ার্ড"
}
],
"label.passwords-dont-match": [
{
"type": 0,
"value": "পাসওয়ার্ড মেলে না"
}
],
"label.profile": [
{
"type": 0,
"value": "প্রোফাইল"
}
],
"label.realtime": [
{
"type": 0,
"value": "সরাসরি"
}
],
"label.realtime-logs": [
{
"type": 0,
"value": "সরাসরি লগ"
}
],
"label.refresh": [
{
"type": 0,
"value": "রিফ্রেশ"
}
],
"label.required": [
{
"type": 0,
"value": "প্রয়োজনীয়"
}
],
"label.reset": [
{
"type": 0,
"value": "রিসেট"
}
],
"label.reset-website": [
{
"type": 0,
"value": "ওয়েবসাইট রিসেট করুন"
}
],
"label.save": [
{
"type": 0,
"value": "সংরক্ষণ"
}
],
"label.settings": [
{
"type": 0,
"value": "সেটিংস"
}
],
"label.share-url": [
{
"type": 0,
"value": "ইউআরএল শেয়ার করুন"
}
],
"label.single-day": [
{
"type": 0,
"value": "একদিন"
}
],
"label.theme": [
{
"type": 0,
"value": "থিম"
}
],
"label.this-month": [
{
"type": 0,
"value": "এই মাস"
}
],
"label.this-week": [
{
"type": 0,
"value": "এই সপ্তাহ"
}
],
"label.this-year": [
{
"type": 0,
"value": "এই বছর"
}
],
"label.timezone": [
{
"type": 0,
"value": "সময়স্থান"
}
],
"label.today": [
{
"type": 0,
"value": "আজ"
}
],
"label.tracking-code": [
{
"type": 0,
"value": "ট্র্যাকিং কোড"
}
],
"label.unknown": [
{
"type": 0,
"value": "অজানা"
}
],
"label.username": [
{
"type": 0,
"value": "ব্যবহারকারীর নাম"
}
],
"label.view-details": [
{
"type": 0,
"value": "বিস্তারিত দেখুন"
}
],
"label.websites": [
{
"type": 0,
"value": "সবগুলো ওয়েবসাইট"
}
],
"message.active-users": [
{
"type": 1,
"value": "x"
},
{
"type": 0,
"value": " বর্তমান "
},
{
"offset": 0,
"options": {
"one": {
"value": [
{
"type": 0,
"value": "visitor"
}
]
},
"other": {
"value": [
{
"type": 0,
"value": "visitors"
}
]
}
},
"pluralType": "cardinal",
"type": 6,
"value": "x"
}
],
"message.confirm-delete": [
{
"type": 0,
"value": "আপনি কি নিশ্চিত যে আপনি "
},
{
"type": 1,
"value": "target"
},
{
"type": 0,
"value": " মুছতে চান?"
}
],
"message.confirm-reset": [
{
"type": 0,
"value": "আপনি কি নিশ্চিত যে আপনি "
},
{
"type": 1,
"value": "target"
},
{
"type": 0,
"value": " এর পরিসংখ্যান পুনরায় সেট করতে চান?"
}
],
"message.copied": [
{
"type": 0,
"value": "কপি হয়েছে"
}
],
"message.delete-warning": [
{
"type": 0,
"value": "সমস্ত সম্পর্কিত ডেটা পাশাপাশি মুছে ফেলা হবে।"
}
],
"message.edit-dashboard": [
{
"type": 0,
"value": "Edit dashboard"
}
],
"message.failure": [
{
"type": 0,
"value": "কিছু ভুল হয়েছে।"
}
],
"message.get-share-url": [
{
"type": 0,
"value": "শেয়ার ইউআরএল"
}
],
"message.get-tracking-code": [
{
"type": 0,
"value": "ট্র্যাকিং কোড পান"
}
],
"message.go-to-settings": [
{
"type": 0,
"value": "সেটিংস এ যান"
}
],
"message.incorrect-username-password": [
{
"type": 0,
"value": "ভুল ব্যবহারকারীর নাম/পাসওয়ার্ড।"
}
],
"message.log.visitor": [
{
"type": 1,
"value": "country"
},
{
"type": 0,
"value": " থেকে একজন ভিসিটর "
},
{
"type": 1,
"value": "ব্রাউজার"
},
{
"type": 0,
"value": ", ব্যবহার করছেন "
},
{
"type": 1,
"value": "os"
},
{
"type": 0,
"value": " "
},
{
"type": 1,
"value": "device"
},
{
"type": 0,
"value": " এর মধ্যে।"
}
],
"message.new-version-available": [
{
"type": 0,
"value": "নতুন সংস্করণ "
},
{
"type": 1,
"value": "version"
},
{
"type": 0,
"value": " পাওয়া গিয়েছে।"
}
],
"message.no-data-available": [
{
"type": 0,
"value": "কোন তথ্য নেই।"
}
],
"message.no-websites-configured": [
{
"type": 0,
"value": "কোনও ওয়েবসাইট কনফিগার করা নেই।"
}
],
"message.page-not-found": [
{
"type": 0,
"value": "পৃষ্ঠা খুঁজে পাওয়া যায়নি।"
}
],
"message.powered-by": [
{
"type": 1,
"value": "name"
},
{
"type": 0,
"value": " দ্বারা চালিত"
}
],
"message.reset-warning": [
{
"type": 0,
"value": "এই ওয়েবসাইটের সমস্ত পরিসংখ্যান মুছে ফেলা হবে, তবে আপনার ট্র্যাকিং কোডটি অক্ষত থাকবে।"
}
],
"message.save-success": [
{
"type": 0,
"value": "সংরক্ষিত হয়েছে।"
}
],
"message.share-url": [
{
"type": 0,
"value": "এটি "
},
{
"type": 1,
"value": "target"
},
{
"type": 0,
"value": " এর জন্য প্রকাশ্যে শেয়ার করার ইউআরএল।"
}
],
"message.toggle-charts": [
{
"type": 0,
"value": "চার্ট পরিবর্তন করুন"
}
],
"message.track-stats": [
{
"type": 1,
"value": "target"
},
{
"type": 0,
"value": " এর জন্য পরিসংখ্যানগুলি ট্র্যাক করতে, আপনার ওয়েবসাইটের "
},
{
"type": 1,
"value": "head"
},
{
"type": 0,
"value": " বিভাগে নিম্নলিখিত কোডটি রাখুন।"
}
],
"message.type-delete": [
{
"type": 0,
"value": "নিশ্চিত করতে নীচের বাক্সে "
},
{
"type": 1,
"value": "delete"
},
{
"type": 0,
"value": " টাইপ করুন।"
}
],
"message.type-reset": [
{
"type": 0,
"value": "নিশ্চিত করতে নীচের বাক্সে "
},
{
"type": 1,
"value": "reset"
},
{
"type": 0,
"value": " টাইপ করুন।"
}
],
"metrics.actions": [
{
"type": 0,
"value": "অ্যাকশনস"
}
],
"metrics.average-visit-time": [
{
"type": 0,
"value": "গড় পরিদর্শনের সময়"
}
],
"metrics.bounce-rate": [
{
"type": 0,
"value": "বহিষ্কারের হার"
}
],
"metrics.browsers": [
{
"type": 0,
"value": "ব্রাউজার"
}
],
"metrics.countries": [
{
"type": 0,
"value": "দেশ"
}
],
"metrics.device.desktop": [
{
"type": 0,
"value": "ডেস্কটপ"
}
],
"metrics.device.laptop": [
{
"type": 0,
"value": "ল্যাপটপ"
}
],
"metrics.device.mobile": [
{
"type": 0,
"value": "মুঠোফোন"
}
],
"metrics.device.tablet": [
{
"type": 0,
"value": "ট্যাবলেট"
}
],
"metrics.devices": [
{
"type": 0,
"value": "ডিভাইস গুলো"
}
],
"metrics.events": [
{
"type": 0,
"value": "ঘটনা"
}
],
"metrics.filter.combined": [
{
"type": 0,
"value": "সম্মিলিত"
}
],
"metrics.filter.raw": [
{
"type": 0,
"value": "অপরিশোধিত"
}
],
"metrics.languages": [
{
"type": 0,
"value": "ভাষা"
}
],
"metrics.operating-systems": [
{
"type": 0,
"value": "অপারেটিং সিস্টেম গুলো"
}
],
"metrics.page-views": [
{
"type": 0,
"value": "পৃষ্ঠা পরিদর্শন গুলো"
}
],
"metrics.pages": [
{
"type": 0,
"value": "পৃষ্ঠাগুলি"
}
],
"metrics.referrers": [
{
"type": 0,
"value": "রেফারার্স"
}
],
"metrics.screens": [
{
"type": 0,
"value": "স্ক্রিনগুলি"
}
],
"metrics.unique-visitors": [
{
"type": 0,
"value": "অনন্য ভিজিটর"
}
],
"metrics.utm": [
{
"type": 0,
"value": "UTM"
}
],
"metrics.utm_campaign": [
{
"type": 0,
"value": "UTM প্রচার"
}
],
"metrics.utm_content": [
{
"type": 0,
"value": "UTM সামগ্রী"
}
],
"metrics.utm_medium": [
{
"type": 0,
"value": "UTM মিডিয়াম"
}
],
"metrics.utm_source": [
{
"type": 0,
"value": "UTM উৎস"
}
],
"metrics.utm_term": [
{
"type": 0,
"value": "UTM শব্দ"
}
],
"metrics.views": [
{
"type": 0,
"value": "ভিউস"
}
],
"metrics.visitors": [
{
"type": 0,
"value": "পরিদর্শনার্থী"
}
]
}

View File

@ -477,6 +477,12 @@
"value": "També s'esborraran totes les dades relacionades." "value": "També s'esborraran totes les dades relacionades."
} }
], ],
"message.edit-dashboard": [
{
"type": 0,
"value": "Edit dashboard"
}
],
"message.failure": [ "message.failure": [
{ {
"type": 0, "type": 0,

View File

@ -477,6 +477,12 @@
"value": "Všechna související data budou také smazána." "value": "Všechna související data budou také smazána."
} }
], ],
"message.edit-dashboard": [
{
"type": 0,
"value": "Edit dashboard"
}
],
"message.failure": [ "message.failure": [
{ {
"type": 0, "type": 0,

View File

@ -477,6 +477,12 @@
"value": "Alle tilknyttede data slettes også." "value": "Alle tilknyttede data slettes også."
} }
], ],
"message.edit-dashboard": [
{
"type": 0,
"value": "Edit dashboard"
}
],
"message.failure": [ "message.failure": [
{ {
"type": 0, "type": 0,

View File

@ -250,7 +250,7 @@
"label.none": [ "label.none": [
{ {
"type": 0, "type": 0,
"value": "None" "value": "Keine"
} }
], ],
"label.owner": [ "label.owner": [
@ -477,6 +477,12 @@
"value": "Alle zugehörigen Daten werden ebenfalls gelöscht." "value": "Alle zugehörigen Daten werden ebenfalls gelöscht."
} }
], ],
"message.edit-dashboard": [
{
"type": 0,
"value": "Edit dashboard"
}
],
"message.failure": [ "message.failure": [
{ {
"type": 0, "type": 0,
@ -794,13 +800,13 @@
"metrics.utm_campaign": [ "metrics.utm_campaign": [
{ {
"type": 0, "type": 0,
"value": "UTM Campaign" "value": "UTM Kampagne"
} }
], ],
"metrics.utm_content": [ "metrics.utm_content": [
{ {
"type": 0, "type": 0,
"value": "UTM Content" "value": "UTM Inhalt"
} }
], ],
"metrics.utm_medium": [ "metrics.utm_medium": [
@ -812,13 +818,13 @@
"metrics.utm_source": [ "metrics.utm_source": [
{ {
"type": 0, "type": 0,
"value": "UTM Source" "value": "UTM Quelle"
} }
], ],
"metrics.utm_term": [ "metrics.utm_term": [
{ {
"type": 0, "type": 0,
"value": "UTM Term" "value": "UTM Begriff"
} }
], ],
"metrics.views": [ "metrics.views": [

View File

@ -477,6 +477,12 @@
"value": "Όλα τα σχετικά δεδομένα θα διαγραφούν επίσης." "value": "Όλα τα σχετικά δεδομένα θα διαγραφούν επίσης."
} }
], ],
"message.edit-dashboard": [
{
"type": 0,
"value": "Edit dashboard"
}
],
"message.failure": [ "message.failure": [
{ {
"type": 0, "type": 0,

View File

@ -477,6 +477,12 @@
"value": "All associated data will be deleted as well." "value": "All associated data will be deleted as well."
} }
], ],
"message.edit-dashboard": [
{
"type": 0,
"value": "Edit dashboard"
}
],
"message.failure": [ "message.failure": [
{ {
"type": 0, "type": 0,

View File

@ -477,6 +477,12 @@
"value": "All associated data will be deleted as well." "value": "All associated data will be deleted as well."
} }
], ],
"message.edit-dashboard": [
{
"type": 0,
"value": "Edit dashboard"
}
],
"message.failure": [ "message.failure": [
{ {
"type": 0, "type": 0,

View File

@ -477,6 +477,12 @@
"value": "Toda la información relacionada será eliminada." "value": "Toda la información relacionada será eliminada."
} }
], ],
"message.edit-dashboard": [
{
"type": 0,
"value": "Edit dashboard"
}
],
"message.failure": [ "message.failure": [
{ {
"type": 0, "type": 0,

View File

@ -477,6 +477,12 @@
"value": "همه‌ی داده‌های مرتبط هم حذف خواهد شد." "value": "همه‌ی داده‌های مرتبط هم حذف خواهد شد."
} }
], ],
"message.edit-dashboard": [
{
"type": 0,
"value": "Edit dashboard"
}
],
"message.failure": [ "message.failure": [
{ {
"type": 0, "type": 0,

View File

@ -477,6 +477,12 @@
"value": "Kaikki siihen liittyvät tiedot poistetaan." "value": "Kaikki siihen liittyvät tiedot poistetaan."
} }
], ],
"message.edit-dashboard": [
{
"type": 0,
"value": "Edit dashboard"
}
],
"message.failure": [ "message.failure": [
{ {
"type": 0, "type": 0,

View File

@ -477,6 +477,12 @@
"value": "Øll data ið er knýtt at verður eisini strika." "value": "Øll data ið er knýtt at verður eisini strika."
} }
], ],
"message.edit-dashboard": [
{
"type": 0,
"value": "Edit dashboard"
}
],
"message.failure": [ "message.failure": [
{ {
"type": 0, "type": 0,

View File

@ -473,6 +473,12 @@
"value": "Toutes les données associées seront également supprimées." "value": "Toutes les données associées seront également supprimées."
} }
], ],
"message.edit-dashboard": [
{
"type": 0,
"value": "Edit dashboard"
}
],
"message.failure": [ "message.failure": [
{ {
"type": 0, "type": 0,

View File

@ -477,6 +477,12 @@
"value": "Tamén serán borrados tódolos datos asociados." "value": "Tamén serán borrados tódolos datos asociados."
} }
], ],
"message.edit-dashboard": [
{
"type": 0,
"value": "Edit dashboard"
}
],
"message.failure": [ "message.failure": [
{ {
"type": 0, "type": 0,

View File

@ -469,6 +469,12 @@
"value": "כל המידע המקושר יימחק" "value": "כל המידע המקושר יימחק"
} }
], ],
"message.edit-dashboard": [
{
"type": 0,
"value": "Edit dashboard"
}
],
"message.failure": [ "message.failure": [
{ {
"type": 0, "type": 0,

Some files were not shown because too many files have changed in this diff Show More