Compare commits

..

4 Commits

Author SHA1 Message Date
Cedric Verstraeten
6672535544 fix glitch with loading livestream when hitting dashboard page first 2023-06-14 17:14:40 +02:00
Cedric Verstraeten
ed397b6ecc change environment box sizes 2023-06-14 16:59:26 +02:00
Cedric Verstraeten
530e4c654e set permissions to modify the env.js file on runtime 2023-06-14 16:49:50 +02:00
Cedric Verstraeten
913bd1ba12 add demo environment mode 2023-06-14 16:29:13 +02:00
12 changed files with 310 additions and 175 deletions

View File

@@ -122,6 +122,7 @@ RUN cp /home/agent/data/config/config.json /home/agent/data/config.template.json
# Set permissions correctly
RUN chown -R agent:kerberosio /home/agent/data
RUN chown -R agent:kerberosio /home/agent/www
###########################
# Grant the necessary root capabilities to the process trying to bind to the privileged port

View File

@@ -165,6 +165,7 @@ Next to attaching the configuration file, it is also possible to override the co
| Name | Description | Default Value |
| --------------------------------------- | ----------------------------------------------------------------------------------------------- | ------------------------------ |
| `AGENT_MODE` | You can choose to run this in 'release' for production, and or 'demo' for showcasing. | "release" |
| `AGENT_USERNAME` | The username used to authenticate against the Kerberos Agent login page. | "root" |
| `AGENT_PASSWORD` | The password used to authenticate against the Kerberos Agent login page. | "root" |
| `AGENT_KEY` | A unique identifier for your Kerberos Agent, this is auto-generated but can be overriden. | "" |

View File

@@ -1,6 +1,8 @@
package http
import (
"os"
jwt "github.com/appleboy/gin-jwt/v2"
"github.com/gin-contrib/pprof"
"github.com/gin-gonic/contrib/static"
@@ -57,6 +59,17 @@ func StartServer(configuration *models.Configuration, communication *models.Comm
// Add all routes
AddRoutes(r, authMiddleware, configuration, communication)
// Update environment variables
environmentVariables := "./www/env.js"
if os.Getenv("AGENT_MODE") == "demo" {
demoEnvironmentVariables := "./www/env.demo.js"
// Move demo environment variables to environment variables
err := os.Rename(demoEnvironmentVariables, environmentVariables)
if err != nil {
log.Fatal(err)
}
}
// Add static routes to UI
r.Use(static.Serve("/", static.LocalFile("./www", true)))
r.Use(static.Serve("/dashboard", static.LocalFile("./www", true)))

View File

@@ -21,6 +21,7 @@
"react/forbid-prop-types": "off",
"no-case-declarations": "off",
"camelcase": "off",
"dot-notation": "off",
"jsx-a11y/media-has-caption": "off",
"jsx-a11y/anchor-is-valid": "off",
"jsx-a11y/click-events-have-key-events": "off",

5
ui/public/env.demo.js Normal file
View File

@@ -0,0 +1,5 @@
(function (window) {
window['env'] = window['env'] || {};
// Environment variables
window['env']['mode'] = 'demo';
})(this);

5
ui/public/env.js Normal file
View File

@@ -0,0 +1,5 @@
(function (window) {
window['env'] = window['env'] || {};
// Environment variables
window['env']['mode'] = 'release';
})(this);

View File

@@ -19,7 +19,7 @@
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<script src="assets/env.js"></script>
<script src="%PUBLIC_URL%/env.js"></script>
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.

View File

@@ -93,111 +93,122 @@ class App extends React.Component {
const { children, username, dashboard, dispatchLogout } = this.props;
const cloudOnline = this.getCurrentTimestamp() - dashboard.cloudOnline < 30;
return (
<div id="page-root">
<Sidebar logo={logo} title="Kerberos Agent" version="v1-beta" mobile>
<Profilebar
username={username}
email="support@kerberos.io"
userrole={t('navigation.admin')}
logout={dispatchLogout}
/>
<Navigation>
<NavigationSection title={t('navigation.management')} />
<NavigationGroup>
<NavigationItem
title={t('navigation.dashboard')}
icon="dashboard"
link="dashboard"
/>
<NavigationItem
title={t('navigation.recordings')}
icon="media"
link="media"
/>
<NavigationItem
title={t('navigation.settings')}
icon="preferences"
link="settings"
/>
</NavigationGroup>
<NavigationSection title={t('navigation.help_support')} />
<NavigationGroup>
<NavigationItem
title={t('navigation.swagger')}
icon="api"
external
link={`${config.URL}/swagger/index.html`}
/>
<NavigationItem
title={t('navigation.documentation')}
icon="book"
external
link="https://doc.kerberos.io/agent/announcement"
/>
<NavigationItem
title="Kerberos Hub"
icon="cloud"
external
link="https://app.kerberos.io"
/>
<NavigationItem
title={t('navigation.ui_library')}
icon="paint"
external
link="https://ui.kerberos.io/"
/>
<NavigationItem
title="Github"
icon="github-nav"
external
link="https://github.com/kerberos-io/agent"
/>
</NavigationGroup>
<NavigationSection title={t('navigation.layout')} />
<NavigationGroup>
<LanguageSelect />
</NavigationGroup>
<NavigationSection title="Websocket" />
<NavigationGroup>
<div className="websocket-badge">
<Badge
title={connected ? 'connected' : 'disconnected'}
status={connected ? 'success' : 'warning'}
<>
{config.MODE !== 'release' && (
<div className={`environment ${config.MODE}`}>
Environment: {config.MODE}
</div>
)}
<div id="page-root">
<Sidebar logo={logo} title="Kerberos Agent" version="v1-beta" mobile>
<Profilebar
username={username}
email="support@kerberos.io"
userrole={t('navigation.admin')}
logout={dispatchLogout}
/>
<Navigation>
<NavigationSection title={t('navigation.management')} />
<NavigationGroup>
<NavigationItem
title={t('navigation.dashboard')}
icon="dashboard"
link="dashboard"
/>
</div>
</NavigationGroup>
</Navigation>
</Sidebar>
<Main>
<Gradient />
<NavigationItem
title={t('navigation.recordings')}
icon="media"
link="media"
/>
<NavigationItem
title={t('navigation.settings')}
icon="preferences"
link="settings"
/>
</NavigationGroup>
<NavigationSection title={t('navigation.help_support')} />
<NavigationGroup>
<NavigationItem
title={t('navigation.swagger')}
icon="api"
external
link={`${config.URL}/swagger/index.html`}
/>
<NavigationItem
title={t('navigation.documentation')}
icon="book"
external
link="https://doc.kerberos.io/agent/announcement"
/>
<NavigationItem
title="Kerberos Hub"
icon="cloud"
external
link="https://app.kerberos.io"
/>
<NavigationItem
title={t('navigation.ui_library')}
icon="paint"
external
link="https://ui.kerberos.io/"
/>
<NavigationItem
title="Github"
icon="github-nav"
external
link="https://github.com/kerberos-io/agent"
/>
</NavigationGroup>
<NavigationSection title={t('navigation.layout')} />
<NavigationGroup>
<LanguageSelect />
</NavigationGroup>
{!cloudOnline && (
<a href="https://app.kerberos.io" target="_blank" rel="noreferrer">
<div className="cloud-not-installed">
<div>
<Icon label="cloud" />
Activate Kerberos Hub, and make your cameras and recordings
available through a secured cloud!
<NavigationSection title="Websocket" />
<NavigationGroup>
<div className="websocket-badge">
<Badge
title={connected ? 'connected' : 'disconnected'}
status={connected ? 'success' : 'warning'}
/>
</div>
</div>
</a>
)}
</NavigationGroup>
</Navigation>
</Sidebar>
<Main>
<Gradient />
{dashboard.offlineMode === 'true' && (
<Link to="/settings">
<div className="offline-mode">
<div>
<Icon label="info" />
Attention! Kerberos is currently running in Offline mode.
{!cloudOnline && (
<a
href="https://app.kerberos.io"
target="_blank"
rel="noreferrer"
>
<div className="cloud-not-installed">
<div>
<Icon label="cloud" />
Activate Kerberos Hub, and make your cameras and recordings
available through a secured cloud!
</div>
</div>
</div>
</Link>
)}
</a>
)}
<MainBody>{children}</MainBody>
</Main>
</div>
{dashboard.offlineMode === 'true' && (
<Link to="/settings">
<div className="offline-mode">
<div>
<Icon label="info" />
Attention! Kerberos is currently running in Offline mode.
</div>
</div>
</Link>
)}
<MainBody>{children}</MainBody>
</Main>
</div>
</>
);
}
}

View File

@@ -12,6 +12,7 @@ const dev = {
API_URL: `${protocol}//${hostname}:8080/api`,
URL: `${protocol}//${hostname}:8080`,
WS_URL: `${websocketprotocol}//${hostname}:8080/ws`,
MODE: window['env']['mode'],
// Uncomment, and comment the above lines, when using codespaces or other special DNS names (which you can't control)
// HOSTNAME: externalHost,
// API_URL: `${protocol}//${externalHost}/api`,
@@ -25,6 +26,7 @@ const prod = {
API_URL: `${protocol}//${host}/api`,
URL: `${protocol}//${host}`,
WS_URL: `${websocketprotocol}//${host}/ws`,
MODE: window['env']['mode'],
};
const config = process.env.REACT_APP_ENVIRONMENT === 'production' ? prod : dev;

View File

@@ -144,3 +144,55 @@ body {
padding: 0;
font-family: 'Inter', sans-serif; // use regular Inter font by default..
}
.environment.develop {
background: repeating-linear-gradient(
-55deg,
#222,
#222 10px,
#035e1f 10px,
#035e1f 20px
);
text-align: center;
color: rgba(255, 255, 255, 0.75);
padding: size(1) 0;
}
.environment.demo {
background: repeating-linear-gradient(
-55deg,
#222,
#222 10px,
#035e1f 10px,
#035e1f 20px
);
text-align: center;
color: rgba(255, 255, 255, 0.75);
padding: 12px 0;
}
.environment.staging {
background: repeating-linear-gradient(
-55deg,
#222,
#222 10px,
#6d0e0e 10px,
#6d0e0e 20px
);
text-align: center;
color: white;
padding: 12px 0;
}
.environment.acceptance {
background: repeating-linear-gradient(
-55deg,
#222,
#222 10px,
#8b8203 10px,
#8b8203 20px
);
text-align: center;
color: white;
padding: 12px 0;
}

View File

@@ -37,6 +37,7 @@ class Dashboard extends React.Component {
currentRecording: '',
initialised: false,
};
this.initialiseLiveview = this.initialiseLiveview.bind(this);
}
componentDidMount() {
@@ -48,30 +49,11 @@ class Dashboard extends React.Component {
});
});
}
this.initialiseLiveview();
}
componentDidUpdate() {
const { initialised } = this.state;
if (!initialised) {
const { connected, dispatchSend } = this.props;
const message = {
message_type: 'stream-sd',
};
if (connected) {
dispatchSend(message);
}
const requestStreamInterval = interval(2000);
this.requestStreamSubscription = requestStreamInterval.subscribe(() => {
if (connected) {
dispatchSend(message);
}
});
this.setState({
initialised: true,
});
}
this.initialiseLiveview();
}
componentWillUnmount() {
@@ -101,6 +83,30 @@ class Dashboard extends React.Component {
return Math.round(Date.now() / 1000);
}
initialiseLiveview() {
const { initialised } = this.state;
if (!initialised) {
const message = {
message_type: 'stream-sd',
};
const { connected, dispatchSend } = this.props;
if (connected) {
dispatchSend(message);
}
const requestStreamInterval = interval(2000);
this.requestStreamSubscription = requestStreamInterval.subscribe(() => {
const { connected: isConnected } = this.props;
if (isConnected) {
dispatchSend(message);
}
});
this.setState({
initialised: true,
});
}
}
openModal(file) {
this.setState({
open: true,

View File

@@ -49,60 +49,98 @@ class Login extends React.Component {
const { loginError, error } = this.props;
return (
<LandingLayout
title="Kerberos Agent"
version={config.VERSION}
description="Video surveillance for everyone"
>
<section className="login-body">
<Block>
<form onSubmit={this.handleSubmit} noValidate>
<BlockHeader>
<div>
<Icon label="login" /> <h4>Login</h4>
</div>
</BlockHeader>
{loginError && (
<AlertMessage
message={error}
onClick={() => this.hideMessage()}
/>
)}
<BlockBody>
<Input
label="username or email"
placeholder="Your username/email"
readonly={false}
disabled={false}
type="text"
name="username"
iconleft="accounts"
/>
<Input
label="password"
placeholder="Your password"
readonly={false}
disabled={false}
type="password"
name="password"
iconleft="locked"
iconright="activity"
seperate
/>
</BlockBody>
<BlockFooter>
<p />
<Button
buttonType="submit"
type="submit"
icon="logout"
label="Login"
/>
</BlockFooter>
</form>
</Block>
</section>
</LandingLayout>
<>
{config.MODE !== 'release' && (
<div className={`environment ${config.MODE}`}>
Environment: {config.MODE}
</div>
)}
<LandingLayout
title="Kerberos Agent"
version={config.VERSION}
description="Video surveillance for everyone"
>
<section className="login-body">
<Block>
<form onSubmit={this.handleSubmit} noValidate>
<BlockHeader>
<div>
<Icon label="login" /> <h4>Login</h4>
</div>
</BlockHeader>
{loginError && (
<AlertMessage
message={error}
onClick={() => this.hideMessage()}
/>
)}
<BlockBody>
{config.MODE === 'demo' && (
<>
<Input
label="username or email"
placeholder="Your username/email"
readonly
disabled={false}
value="root"
type="text"
name="username"
iconleft="accounts"
/>
<Input
label="password"
placeholder="Your password"
readonly
disabled={false}
value="root"
type="password"
name="password"
iconleft="locked"
iconright="activity"
seperate
/>
</>
)}
{config.MODE !== 'demo' && (
<>
<Input
label="username or email"
placeholder="Your username/email"
readonly={false}
disabled={false}
type="text"
name="username"
iconleft="accounts"
/>
<Input
label="password"
placeholder="Your password"
readonly={false}
disabled={false}
type="password"
name="password"
iconleft="locked"
iconright="activity"
seperate
/>
</>
)}
</BlockBody>
<BlockFooter>
<p />
<Button
buttonType="submit"
type="submit"
icon="logout"
label="Login"
/>
</BlockFooter>
</form>
</Block>
</section>
</LandingLayout>
</>
);
}
}