Compare commits

..

70 Commits

Author SHA1 Message Date
Ronald A. Richardson
dd65ee619b upgraded to v0.3.4 2023-12-27 12:01:39 +08:00
Ron
4f4fdeaafc Merge pull request #176 from fleetbase/dev-v0.3.4
v0.3.4
2023-12-27 11:36:11 +08:00
Ronald A. Richardson
39747601d0 merged with main 2023-12-27 11:29:43 +08:00
Ronald A. Richardson
321ef64229 added truncate to open issues on github card component 2023-12-27 11:27:35 +08:00
Ronald A. Richardson
1b8f7a663d upgraded to core-api v1.3.5 2023-12-27 11:26:43 +08:00
Ronald A. Richardson
e790a0e123 upgraded to v0.3.3 2023-12-27 11:07:33 +08:00
Ronald A. Richardson
2a5a68f620 (fix) urgently replace deprecated route transitionTo with router service method 2023-12-27 11:05:15 +08:00
Ron
3033f6f4cf Merge pull request #175 from fleetbase/dev-v0.3.3
v0.3.3
2023-12-26 12:46:40 +08:00
Ronald A. Richardson
9fbc3252c6 upgraded core-api and fleetops which includes critical patches and fixes 2023-12-26 12:42:04 +08:00
Ron
25ce216e3b Merge pull request #165 from fleetbase/dev-v0.3.2
v0.3.2
2023-12-25 15:20:12 +08:00
Ronald A. Richardson
6bf3dbad2d move gcp required app.yaml to console dir 2023-12-25 15:07:18 +08:00
Ron
9a8c67a1b7 Merge pull request #168 from ekini/feature/gcp
Add Helm chart and GCP CI/CD pipeline
2023-12-25 15:03:25 +08:00
Ronald A. Richardson
eabd83c8ad upgraded all dependencies to latest releases 2023-12-25 15:03:04 +08:00
Ronald A. Richardson
e4f64021d7 Added gcs storage as a filesystem driver, updated cors to automatically handle www. in domains 2023-12-12 18:19:28 +08:00
Eugene Dementyev
941c6d03d9 Add socketcluster env vars 2023-12-12 18:41:42 +13:00
Eugene Dementyev
2331c6902e Fix helm port 2023-12-11 21:08:43 +13:00
Eugene Dementyev
be9faea3bc Fix repository 2023-12-11 21:05:30 +13:00
Eugene Dementyev
e8d7c021f5 Fix socketcluster 2023-12-11 21:05:03 +13:00
Eugene Dementyev
8c86eed5fc Update socketcluster 2023-12-11 20:59:11 +13:00
Eugene Dementyev
a2ce7b5a65 Fix repository for helm 2023-12-11 20:26:20 +13:00
Ronald A. Richardson
48069177df Upgrade to 5.4 semi successful, working through deprecations 2023-12-08 13:52:38 +08:00
Ronald A. Richardson
c5574a25ae upgraded console to ember 5.4.1 2023-12-07 11:48:55 +08:00
Ronald A. Richardson
0a3ad2f0a6 added scripts 2023-12-07 11:17:52 +08:00
Ron
d60760104d Merge pull request #163 from fleetbase/feature/update-ignore-file
updated lockfile to ignore private packages
2023-12-07 11:02:50 +08:00
Ronald A. Richardson
88b4acff67 working on upgrade to ember 5.4.1 2023-12-07 10:56:09 +08:00
Eugene Dementyev
4d2179129d Add GCP ci/cd
Add helm chart
2023-12-06 08:01:34 +13:00
Shiv Thakker
5c7e0a1c56 Update README.md 2023-11-28 15:08:12 +08:00
Ronald A. Richardson
65a6894d4b updated lockfile to ignore private packages 2023-11-28 11:46:22 +08:00
Ronald A. Richardson
723e3ca3d1 fix composer file 2023-11-27 10:23:44 +08:00
Ronald A. Richardson
4eb706d33e upgraded to v0.3.1 / cloud version 2023-11-27 10:19:56 +08:00
Ron
e3acd28c18 Merge pull request #162 from fleetbase/dev-v0.3.1
v0.3.1
2023-11-24 17:39:37 +08:00
Ronald A. Richardson
313b6e63a8 added other extensions 2023-11-24 17:30:53 +08:00
Ronald A. Richardson
a7ed7ee935 resolved conflicts for merge 2023-11-24 17:25:37 +08:00
Ronald A. Richardson
1e28d9d8d8 Upgraded dependencies with patches and improvements for improved UX 2023-11-24 17:20:03 +08:00
Ron
0c31b54fde Merge pull request #159 from fleetbase/fix-deploy-workflow
Fix CD workflow
2023-11-22 16:40:41 +08:00
Ronald A. Richardson
f6b83e5638 upgraded to aws-actions/configure-aws-credentials@v4 2023-11-22 16:39:04 +08:00
Ronald A. Richardson
b30ee818fc added api/auth.json to gitignore 2023-11-22 16:34:30 +08:00
Ronald A. Richardson
6ec9ad59d3 few patches for flespi extension integration 2023-11-17 13:38:55 +08:00
Ron
10ff2e066b Merge pull request #156 from fleetbase/dev-v0.3.0
v0.3.0
2023-11-16 19:01:14 +08:00
Ronald A. Richardson
f56db88ad6 fleetops: fix filters, improve drawer component w/ state, several patches and upgrades 2023-11-16 18:49:17 +08:00
Ron
033cf5cfe0 Merge pull request #155 from fleetbase/dev-v0.2.9
v0.2.9
2023-11-09 17:21:56 +08:00
Ronald A. Richardson
4a4dc76e60 Added scheduler feature, scope/live map drawer feature, and bug fixes and dependency upgrades 2023-11-09 17:04:10 +08:00
Ron
a52af94b00 Merge pull request #154 from fleetbase/dev-v0.2.8
v0.2.8
2023-11-06 20:12:39 +08:00
Ronald A. Richardson
9c4daf7a68 fix dependencies 2023-11-06 19:55:42 +08:00
Ronald A. Richardson
a8904ba112 Full refactor of management section, upgrade of dependencies 2023-11-06 19:39:11 +08:00
Ronald A. Richardson
6880664d9e update packages to latest commits 2023-10-30 19:57:33 +08:00
Ron
99b30d7f58 Merge pull request #148 from fleetbase/feature-notifications
Feature notifications
2023-10-30 19:56:10 +08:00
Ronald A. Richardson
c8539fd2a0 merged with main 2023-10-30 19:41:23 +08:00
Ronald A. Richardson
cb1aec40fd ready for new release with notification tray and settings 2023-10-30 19:37:37 +08:00
Ronald A. Richardson
b728b366a0 patch notification settings in admin 2023-10-30 19:17:01 +08:00
Ronald A. Richardson
5cfc3f1cc7 add back package.json 2023-10-30 14:58:54 +08:00
Ronald A. Richardson
e7b5282aa3 updated notificaiton setting structure to be more robust 2023-10-30 14:53:19 +08:00
Ronald A. Richardson
7ffb7ac24a Latest updates, moved save buttons to subheader for easy access to save 2023-10-30 12:40:39 +08:00
Ron
6e4a9edd7d Merge pull request #146 from fleetbase/add-notification-settings
Add notification settings to Admin
2023-10-30 11:55:57 +08:00
Ron
d9d01c8bbc Merge branch 'feature-notifications' into add-notification-settings 2023-10-30 11:55:12 +08:00
Ronald A. Richardson
c54d75fa0f hotfix notification read event trigger 2023-10-27 17:50:35 +08:00
Ronald A. Richardson
91904c3836 Patches and improved styling for notification page 2023-10-27 17:35:31 +08:00
Ronald A. Richardson
0e075e3b24 dont check in dependency mgmt files 2023-10-27 15:24:58 +08:00
Ron
c56bea95d0 Merge pull request #140 from fleetbase/dev-v0.2.6
v0.2.6
2023-10-27 10:33:17 +08:00
Ronald A. Richardson
f1cec329bc add back environment files 2023-10-27 10:15:40 +08:00
Ronald A. Richardson
34dc365dd2 Preparing release with latest dependencies 2023-10-27 10:11:33 +08:00
TemuulenBM
74a782f4ea WIP: Notification Settings interface on Admin 2023-10-26 17:54:29 +08:00
TemuulenBM
a8b2042d85 Latest updates for notification tray and page 2023-10-26 11:32:54 +08:00
Temuulen Bayanmunkh
cc52a40660 Almost completed the notifications route, <NotificationTray /> component responds with notifications route changes 2023-10-24 17:58:52 +08:00
Temuulen Bayanmunkh
4fb2dec8c3 Added notification tray component 2023-10-20 18:06:43 +08:00
Ronald A. Richardson
0db3634ab3 checkin environments directory and update API_SECURE to be casted to boolean 2023-10-18 16:34:09 +08:00
Ronald A. Richardson
1c0d324ba9 Fix reset of branding settings 2023-10-14 13:48:21 +08:00
Ron
096cc1097a Merge pull request #139 from fleetbase/dev-v0.2.5
preparing for new release
2023-10-13 18:11:07 +08:00
Ronald A. Richardson
eca881156a add required lockfile back to console 2023-10-13 17:50:07 +08:00
Ronald A. Richardson
40b499bcb2 preparing for new release 2023-10-13 17:40:02 +08:00
110 changed files with 19466 additions and 933 deletions

View File

@@ -35,7 +35,7 @@ jobs:
echo "STACK=$(basename $GITHUB_REF)" >> $GITHUB_ENV
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v2
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_NUMBER }}:role/${{ env.PROJECT }}-${{ env.STACK }}-deployer
role-session-name: github

185
.github/workflows/gcp-cd.yml vendored Normal file
View File

@@ -0,0 +1,185 @@
name: Fleetbase CI/CD
on:
push:
branches: [ "gcpdeploy/*" ]
concurrency:
group: ${{ github.ref }}
cancel-in-progress: true
env:
PROJECT: ${{ vars.PROJECT }}
REGISTRY: ${{ vars.REGISTRY }}
SOCKETCLUSTER_HOST: ${{ vars.SOCKETCLUSTER_HOST }}
API_HOST: ${{ vars.API_HOST }}
K8S_CLUSTER_NAME: ${{ vars.K8S_CLUSTER_NAME }}
K8S_CLUSTER_LOCATION: ${{ vars.K8S_CLUSTER_LOCATION }}
GCP_WORKLOAD_IDENTITY_PROVIDER: ${{ vars.GCP_WORKLOAD_IDENTITY_PROVIDER }}
GCP_SERVICE_ACCOUNT: ${{ vars.GCP_SERVICE_ACCOUNT }}
GCP: "True" # switches docker builds to GCP-style registry
jobs:
build_service:
name: Build and Deploy the Service
runs-on: ubuntu-latest
permissions:
id-token: write # This is required for requesting the JWT
contents: read # This is required for actions/checkout
steps:
- name: Checkout Code
uses: actions/checkout@v3
with:
submodules: recursive
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Set Dynamic ENV Vars
run: |
SHORT_COMMIT=$(echo $GITHUB_SHA | cut -c -8)
echo "VERSION=${SHORT_COMMIT}" >> $GITHUB_ENV
echo "STACK=$(basename $GITHUB_REF)" >> $GITHUB_ENV
echo "REGISTRY_HOST=$(dirname $(dirname $REGISTRY))" >> $GITHUB_ENV
- id: 'auth'
name: 'Authenticate to Google Cloud'
uses: 'google-github-actions/auth@v1'
with:
token_format: "access_token"
create_credentials_file: true
workload_identity_provider: ${{ env.GCP_WORKLOAD_IDENTITY_PROVIDER }}
service_account: ${{ env.GCP_SERVICE_ACCOUNT }}
- name: 'Set up Cloud SDK'
uses: 'google-github-actions/setup-gcloud@v1'
- id: 'get-credentials'
uses: 'google-github-actions/get-gke-credentials@v1'
with:
cluster_name: ${{ env.K8S_CLUSTER_NAME }}
location: ${{ env.K8S_CLUSTER_LOCATION }}
- uses: 'docker/login-action@v3'
with:
registry: ${{ env.REGISTRY_HOST }}
username: 'oauth2accesstoken'
password: '${{ steps.auth.outputs.access_token }}'
- name: Prepare Composer Auth Secret
run: |
if [[ -n "${{ secrets._GITHUB_AUTH_TOKEN }}" ]]; then
echo '{"github-oauth": {"github.com": "'${{ secrets._GITHUB_AUTH_TOKEN }}'"}}' > composer-auth.json
else
echo '{}' > composer-auth.json
fi
- name: nullify ssm-parent config
run: |
# this is needed to disable ssm-parent, which is used on AWS
echo > api/.ssm-parent.yaml
- name: Build and Release
uses: docker/bake-action@v2
env:
REGISTRY: ${{ env.REGISTRY }}
VERSION: ${{ env.VERSION }}
CACHE: type=gha
with:
push: true
files: |
./docker-bake.hcl
- name: deploy with helm
run: |
helm upgrade -i fleetbase infra/helm -n ${{ env.PROJECT }}-${{ env.STACK }} --set image.repository=${{ env.REGISTRY }} --set image.tag=${{ env.VERSION }} --set 'api_host=${{ env.API_HOST }}' --set 'socketcluster_host=${{ env.SOCKETCLUSTER_HOST }}' --set 'ingress.annotations.kubernetes\.io/ingress\.global-static-ip-name=${{ env.PROJECT }}-${{ env.STACK }}'
build_frontend:
name: Build and Deploy the Console
needs: [build_service]
runs-on: ubuntu-latest
permissions:
id-token: write # This is required for requesting the JWT
contents: read # This is required for actions/checkout
steps:
- name: Checkout
uses: actions/checkout@v3
with:
submodules: true
- name: Set Dynamic ENV Vars
run: |
SHORT_COMMIT=$(echo $GITHUB_SHA | cut -c -8)
echo "VERSION=${SHORT_COMMIT}" >> $GITHUB_ENV
echo "STACK=$(basename $GITHUB_REF)" >> $GITHUB_ENV
- id: 'auth'
name: 'Authenticate to Google Cloud'
uses: 'google-github-actions/auth@v1'
with:
token_format: "access_token"
create_credentials_file: true
workload_identity_provider: ${{ env.GCP_WORKLOAD_IDENTITY_PROVIDER }}
service_account: ${{ env.GCP_SERVICE_ACCOUNT }}
- name: 'Set up Cloud SDK'
uses: 'google-github-actions/setup-gcloud@v1'
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: 16
- uses: pnpm/action-setup@v2
name: Install pnpm
id: pnpm-install
with:
version: 8
run_install: false
- name: Get pnpm Store Directory
id: pnpm-cache
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
name: Setup pnpm Cache
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Check for _GITHUB_AUTH_TOKEN and create .npmrc
run: |
if [[ -n "${{ secrets._GITHUB_AUTH_TOKEN }}" ]]; then
echo "//npm.pkg.github.com/:_authToken=${{ secrets._GITHUB_AUTH_TOKEN }}" > .npmrc
fi
working-directory: ./console
- name: Install dependencies
run: pnpm install
working-directory: ./console
- name: Build
env:
SOCKETCLUSTER_HOST: ${{ env.SOCKETCLUSTER_HOST }}
SOCKETCLUSTER_SECURE: "true"
SOCKETCLUSTER_PORT: "443"
API_HOST: ${{ env.API_HOST }}
run: |
set -eu
pnpm build
working-directory: ./console
- name: Deploy Console 🚀
run: |
set -eu
gcloud app deploy --appyaml console/app.yaml console/dist
# leave 2 versions
gcloud app versions list --filter="traffic_split=0" --sort-by '~version' --format 'value(version.id)' | sed '1d' | xargs -r gcloud app versions delete

21
.gitignore vendored
View File

@@ -14,12 +14,21 @@ api/storage/public
api/vendor
api/composer.dev.json
api/composer-install-dev.sh
api/auth.json
act.sh
composer-auth.json
packages/billing-api
packages/billing-engine
packages/flespi-engine
packages/flespi-integration
packages/projectargus-engine
docker/database/*
docker/database/mysql/*
docker/database/mysql/*
.talismanrc
verdaccio/minio
verdaccio/config/htpasswd
verdaccio/storage
# private packages
packages/billing
packages/flespi
packages/loconav
packages/projectargus-engine
# wip
packages/solid
solid
verdaccio

View File

@@ -10,6 +10,9 @@
<a href="https://fleetbase.github.io/guides" rel="nofollow">Fleetbase Documentation →</a>
<br>
<br>
<a href="https://meetings.hubspot.com/shiv-thakker" rel="nofollow">Book a Demo</a>
<br>
<br>
<a href="https://github.com/fleetbase/fleetbase/issues">Report an Issue</a>
·
<a href="https://fleetbase.github.io/api-reference">API Reference</a>

View File

@@ -9,9 +9,10 @@
"license": "MIT",
"require": {
"php": "^7.3|^8.0",
"fleetbase/core-api": "^1.3.1",
"fleetbase/fleetops-api": "^0.2.8",
"fleetbase/storefront-api": "^0.2.0",
"fleetbase/core-api": "^1.3.5",
"fleetbase/fleetops-api": "^0.3.7",
"fleetbase/storefront-api": "^0.2.5",
"fleetbase/billing-api": "^0.0.5",
"fruitcake/laravel-cors": "^2.0",
"guzzlehttp/guzzle": "^7.0.1",
"laravel/framework": "^8.75",
@@ -32,6 +33,12 @@
"nunomaduro/collision": "^5.10",
"phpunit/phpunit": "^9.5.10"
},
"repositories": [
{
"type": "vcs",
"url": "git@github.com:fleetbase/billing.git"
}
],
"autoload": {
"psr-4": {
"App\\": "app/",

1571
api/composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -13,7 +13,7 @@ return [
|
*/
'name' => env('APP_NAME', 'Laravel'),
'name' => env('APP_NAME', 'Fleetbase'),
/*
|--------------------------------------------------------------------------

View File

@@ -1,5 +1,7 @@
<?php
use Fleetbase\Support\Utils;
return [
/*
@@ -19,7 +21,7 @@ return [
'allowed_methods' => ['*'],
'allowed_origins' => ['http://localhost:4200', env('CONSOLE_HOST')],
'allowed_origins' => array_filter(['http://localhost:4200', env('CONSOLE_HOST'), Utils::addWwwToUrl(env('CONSOLE_URL'))]),
'allowed_origins_patterns' => [],

View File

@@ -32,13 +32,13 @@ return [
'local' => [
'driver' => 'local',
'root' => storage_path('app'),
'root' => storage_path('app')
],
'public' => [
'driver' => 'local',
'root' => storage_path('app/public'),
'url' => env('APP_URL').'/storage',
'url' => env('APP_URL') . '/storage',
],
's3' => [
@@ -51,6 +51,14 @@ return [
'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
],
'gcs' => [
'driver' => 'gcs',
'project_id' => env('GOOGLE_CLOUD_PROJECT_ID', 'your-project-id'),
'key_file' => env('GOOGLE_CLOUD_KEY_FILE', null),
'bucket' => env('GOOGLE_CLOUD_STORAGE_BUCKET', env('AWS_BUCKET')),
'path_prefix' => env('GOOGLE_CLOUD_STORAGE_PATH_PREFIX', null),
'storage_api_uri' => env('GOOGLE_CLOUD_STORAGE_API_URI', env('AWS_URL')),
],
],
/*
@@ -65,8 +73,7 @@ return [
*/
'links' => [
public_path('storage') => storage_path('app/public'),
public_path('uploads') => storage_path('app/uploads'),
public_path('storage') => storage_path('app/public')
],
];

View File

@@ -60,6 +60,10 @@ return [
'transport' => 'postmark',
],
'sendgrid' => [
'transport' => 'sendgrid',
],
'sendmail' => [
'transport' => 'sendmail',
'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -t -i'),
@@ -96,7 +100,7 @@ return [
'from' => [
'address' => env('MAIL_FROM_ADDRESS', 'hello@fleetbase.io'),
'name' => env('MAIL_FROM_NAME', 'Fleetbase'),
'name' => env('MAIL_FROM_NAME', env('APP_NAME', 'Fleetbase')),
],
/*

View File

@@ -30,4 +30,8 @@ return [
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
],
'sendgrid' => [
'api_key' => env('SENDGRID_API_KEY'),
],
];

View File

@@ -1 +0,0 @@
/var/www/html/api/storage/app/uploads

View File

@@ -1,15 +1,7 @@
{
/**
Ember CLI sends analytics information by default. The data is completely
anonymous, but there are times when you might want to disable this behavior.
Setting `disableAnalytics` to true will prevent any data from being sent.
*/
"disableAnalytics": false,
/**
Setting `isTypeScriptProject` to true will force the blueprint generators to generate TypeScript
rather than JavaScript by default, when a TypeScript version of a given blueprint is available.
Setting `isTypeScriptProject` to true will force the blueprint generators to generate TypeScript
rather than JavaScript by default, when a TypeScript version of a given blueprint is available.
*/
"isTypeScriptProject": false
}

View File

@@ -1,25 +1,13 @@
# unconventional js
/blueprints/*/files/
/vendor/
# compiled output
/dist/
/tmp/
# dependencies
/bower_components/
/node_modules/
# misc
/coverage/
!.*
.*/
.eslintcache
# ember-try
/.node_modules.ember-try/
/bower.json.ember-try
/npm-shrinkwrap.json.ember-try
/package.json.ember-try
/package-lock.json.ember-try
/yarn.lock.ember-try

View File

@@ -2,12 +2,13 @@
module.exports = {
root: true,
parser: 'babel-eslint',
parser: '@babel/eslint-parser',
parserOptions: {
ecmaVersion: 2018,
ecmaVersion: 'latest',
sourceType: 'module',
ecmaFeatures: {
legacyDecorators: true,
requireConfigFile: false,
babelOptions: {
plugins: [['@babel/plugin-proposal-decorators', { decoratorsBeforeExport: true }]],
},
},
plugins: ['ember'],
@@ -50,18 +51,18 @@ module.exports = {
'no-prototype-builtins': 'off',
},
overrides: [
// node files
{
files: [
'./.eslintrc.js',
'./.prettierrc.js',
'./.stylelintrc.js',
'./.template-lintrc.js',
'./ember-cli-build.js',
'./index.js',
'./testem.js',
'./blueprints/*/index.js',
'./config/**/*.js',
'./tests/dummy/config/**/*.js',
'./lib/*/index.js',
'./server/**/*.js',
],
parserOptions: {
sourceType: 'script',
@@ -70,13 +71,7 @@ module.exports = {
browser: false,
node: true,
},
plugins: ['node'],
extends: ['plugin:node/recommended'],
},
{
// test files
files: ['tests/**/*-test.{js,ts}'],
extends: ['plugin:qunit/recommended'],
extends: ['plugin:n/recommended'],
},
],
};

View File

@@ -14,7 +14,7 @@ jobs:
strategy:
matrix:
node-version: [16.x] # Build on Node.js 16
node-version: [18.x] # Build on Node.js 18
steps:
- uses: actions/checkout@v2
@@ -57,4 +57,4 @@ jobs:
run: pnpm run lint
- name: Build
run: npx ember build --environment production
run: npx ember build --environment production

10
console/.gitignore vendored
View File

@@ -1,31 +1,23 @@
# See https://help.github.com/ignore-files/ for more about ignoring files.
# compiled output
/dist/
/tmp/
/declarations/
# dependencies
/bower_components/
/node_modules/
# misc
/.env*
/environments/.env*
/.pnp*
/.sass-cache
/.eslintcache
/connect.lock
/coverage/
/libpeerconnection.log
/npm-debug.log*
/testem.log
/yarn-error.log
/.npmrc
/pnpm-lock.yaml
# ember-try
/.node_modules.ember-try/
/bower.json.ember-try
/npm-shrinkwrap.json.ember-try
/package.json.ember-try
/package-lock.json.ember-try

View File

@@ -1,25 +1,13 @@
# unconventional js
/blueprints/*/files/
/vendor/
# compiled output
/dist/
/tmp/
# dependencies
/bower_components/
/node_modules/
# misc
/coverage/
!.*
.eslintcache
.lint-todo/
.*/
# ember-try
/.node_modules.ember-try/
/bower.json.ember-try
/npm-shrinkwrap.json.ember-try
/package.json.ember-try
/package-lock.json.ember-try
/yarn.lock.ember-try

View File

@@ -8,7 +8,7 @@ module.exports = {
printWidth: 190,
overrides: [
{
files: '*.hbs',
files: '*.{hbs,js,ts}',
options: {
singleQuote: false,
},

8
console/.stylelintignore Normal file
View File

@@ -0,0 +1,8 @@
# unconventional files
/blueprints/*/files/
# compiled output
/dist/
# addons
/.node_modules.ember-try/

5
console/.stylelintrc.js Normal file
View File

@@ -0,0 +1,5 @@
'use strict';
module.exports = {
extends: ['stylelint-config-standard', 'stylelint-prettier/recommended'],
};

View File

@@ -1,3 +1,3 @@
{
"ignore_dirs": ["tmp", "dist"]
"ignore_dirs": ["dist"]
}

12
console/app.yaml Normal file
View File

@@ -0,0 +1,12 @@
runtime: python312
handlers:
- url: /(.*\..+)$
static_files: \1
upload: (.+)
secure: always
expiration: 1h
- url: /.*
static_files: index.html
upload: index.html
secure: always

View File

@@ -14,7 +14,7 @@ export default class App extends Application {
async ready() {
const extensions = await loadExtensions();
this.extensions = extensions;
this.engines = mapEngines(extensions);
}

View File

@@ -16,6 +16,6 @@
<Button @wrapperClass="mt-3" @icon="plug" @text="Test Config" @onClick={{this.test}} @isLoading={{this.isLoading}} />
</ContentPanel>
<div class="mt-3 flex items-center justify-end">
<Button @type="primary" @size="lg" @icon="save" @text="Save Changes" @onClick={{this.save}} @disabled={{this.isLoading}} @isLoading={{this.isLoading}} />
</div>
<EmberWormhole @to="next-view-section-subheader-actions">
<Button @type="primary" @size="sm" @icon="save" @text="Save Changes" @onClick={{this.save}} @disabled={{this.isLoading}} @isLoading={{this.isLoading}} />
</EmberWormhole>

View File

@@ -22,6 +22,6 @@
<Button @wrapperClass="mt-3" @icon="plug" @text="Test Config" @onClick={{this.test}} @isLoading={{this.isLoading}} />
</ContentPanel>
<div class="mt-3 flex items-center justify-end">
<Button @type="primary" @size="lg" @icon="save" @text="Save Changes" @onClick={{this.save}} @disabled={{this.isLoading}} @isLoading={{this.isLoading}} />
</div>
<EmberWormhole @to="next-view-section-subheader-actions">
<Button @type="primary" @size="sm" @icon="save" @text="Save Changes" @onClick={{this.save}} @disabled={{this.isLoading}} @isLoading={{this.isLoading}} />
</EmberWormhole>

View File

@@ -47,6 +47,6 @@
</div>
</ContentPanel>
<div class="mt-3 flex items-center justify-end">
<Button @type="primary" @size="lg" @icon="save" @text="Save Changes" @onClick={{this.save}} @disabled={{this.isLoading}} @isLoading={{this.isLoading}} />
</div>
<EmberWormhole @to="next-view-section-subheader-actions">
<Button @type="primary" @size="sm" @icon="save" @text="Save Changes" @onClick={{this.save}} @disabled={{this.isLoading}} @isLoading={{this.isLoading}} />
</EmberWormhole>

View File

@@ -20,6 +20,6 @@
<Button @wrapperClass="mt-3" @icon="plug" @text="Test Config" @onClick={{this.test}} @isLoading={{this.isLoading}} />
</ContentPanel>
<div class="mt-3 flex items-center justify-end">
<Button @type="primary" @size="lg" @icon="save" @text="Save Changes" @onClick={{this.save}} @disabled={{this.isLoading}} @isLoading={{this.isLoading}} />
</div>
<EmberWormhole @to="next-view-section-subheader-actions">
<Button @type="primary" @size="sm" @icon="save" @text="Save Changes" @onClick={{this.save}} @disabled={{this.isLoading}} @isLoading={{this.isLoading}} />
</EmberWormhole>

View File

@@ -40,6 +40,6 @@
<InputGroup @name="IP Info API Key" @value={{this.ipinfoApiKey}} disabled={{this.isLoading}} />
</ContentPanel>
<div class="mt-3 flex items-center justify-end">
<Button @type="primary" @size="lg" @icon="save" @text="Save Changes" @onClick={{this.save}} @disabled={{this.isLoading}} @isLoading={{this.isLoading}} />
</div>
<EmberWormhole @to="next-view-section-subheader-actions">
<Button @type="primary" @size="sm" @icon="save" @text="Save Changes" @onClick={{this.save}} @disabled={{this.isLoading}} @isLoading={{this.isLoading}} />
</EmberWormhole>

View File

@@ -5,14 +5,14 @@
<Spinner />
</div>
{{else}}
<div class="flex flex-row p-4 border-b dark:border-gray-700 border-gray-200">
<div class="flex flex-row p-3 border-b dark:border-gray-700 border-gray-200">
<div class="w-12 flex-shrink-0"><img src={{this.data.owner.avatar_url}} alt="fleetbase/fleetbase" class="rounded-full w-8 h-8" width="32" height="32" /></div>
<div class="flex-1 -mt-2">
<div class="flex flex-1 flex-row items-center justify-between mb-2">
<a href={{this.data.html_url}} target="_github" class="dark:text-gray-100 text-black text-lg font-semibold">{{this.data.full_name}}</a>
<a href={{this.data.html_url}} target="_github" class="dark:text-gray-100 text-black text-base font-semibold">{{this.data.full_name}}</a>
<a href={{this.data.html_url}} target="_github" class="btn btn-xs btn-default">
<FaIcon @icon="star" class="text-yellow-400" />
<span class="hidden lg:flex ml-2.5">Star on Github</span>
<span class="hidden truncate lg:flex ml-2.5">Star on Github</span>
</a>
</div>
<p class="dark:text-gray-100 text-black text-sm">{{this.data.description}}</p>
@@ -40,7 +40,7 @@
</div>
<div class="col-span-3 dark:text-gray-100 text-black text-xs flex flex-row">
<span class="font-bold mr-1">{{this.data.open_issues_count}}</span>
<span><span class="hidden lg:inline-flex">Open</span> Issues</span>
<span class="truncate"><span class="hidden lg:inline-flex">Open</span> Issues</span>
</div>
</div>
</div>

View File

@@ -33,6 +33,14 @@ export default class AuthLoginController extends Controller {
*/
@service session;
/**
* Inject the `router` service
*
* @var {Service}
*/
@service router;
/**
* Whether or not to remember the users session
*
@@ -124,14 +132,14 @@ export default class AuthLoginController extends Controller {
* Transition user to onboarding screen
*/
@action transitionToOnboard() {
return this.transitionToRoute('onboard');
return this.router.transitionTo('onboard');
}
/**
* Transition to forgot password screen, if email is set - set it.
*/
@action forgotPassword() {
return this.transitionToRoute('auth.forgot-password').then(() => {
return this.router.transitionTo('auth.forgot-password').then(() => {
if (this.email) {
this.forgotPasswordController.email = this.email;
}

View File

@@ -18,6 +18,13 @@ export default class AuthResetPasswordController extends Controller {
*/
@service notifications;
/**
* Inject the `router` service
*
* @memberof AuthResetPasswordController
*/
@service router;
/**
* The code param.
*
@@ -65,7 +72,7 @@ export default class AuthResetPasswordController extends Controller {
.then(() => {
this.notifications.success('Your password has been reset! Login to continue.');
return this.transitionToRoute('auth.login');
return this.router.transitionTo('auth.login');
})
.catch((error) => {
this.notifications.serverError(error);

View File

@@ -65,13 +65,6 @@ export default class ConsoleController extends Controller {
*/
@tracked organizations = [];
/**
* Whether or not to hide the sidebar.
*
* @var {Boolean}
*/
@tracked hideSidebar = true;
/**
* Sidebar Context Controls
*
@@ -79,6 +72,27 @@ export default class ConsoleController extends Controller {
*/
@tracked sidebarContext;
/**
* State of sidebar toggle icon
*
* @var {SidebarContext}
*/
@tracked sidebarToggleEnabled = true;
/**
* The sidebar toggle state.
*
* @var {SidebarContext}
*/
@tracked sidebarToggleState = {};
/**
* Routes which should hide the sidebar menu.
*
* @var {Array}
*/
@tracked hiddenSidebarRoutes = ['console.home', 'console.extensions', 'console.notifications'];
/**
* Installed extensions.
*
@@ -104,15 +118,47 @@ export default class ConsoleController extends Controller {
this.router.on('routeDidChange', (transition) => {
if (this.sidebarContext) {
if (transition.to.name === 'console.home' || transition.to.name === 'console.extensions') {
// Determine if the new route should hide the sidebar
const shouldHideSidebar = this.hiddenSidebarRoutes.includes(transition.to.name);
// Check if the sidebar was manually toggled and is currently closed
const isSidebarManuallyClosed = this.sidebarToggleState.clicked && !this.sidebarToggleState.isOpen;
// Hide the sidebar if the current route is in hiddenSidebarRoutes
if (shouldHideSidebar) {
this.sidebarContext.hideNow();
this.sidebarToggleEnabled = false;
return; // Exit early as no further action is required
}
// If the sidebar was manually closed and not on a hidden route, keep it closed
if (isSidebarManuallyClosed) {
this.sidebarContext.hideNow();
} else {
// Otherwise, show the sidebar
this.sidebarContext.show();
}
// Ensure toggle is enabled unless on a hidden route
this.sidebarToggleEnabled = !shouldHideSidebar;
}
});
}
/**
* When sidebar is manually toggled
*
* @param {SidebarContext} sidebar
* @param {boolean} isOpen
* @memberof ConsoleController
*/
@action onSidebarToggle(sidebar, isOpen) {
this.sidebarToggleState = {
clicked: true,
isOpen
};
}
/**
* Sets the sidebar context
*
@@ -123,8 +169,9 @@ export default class ConsoleController extends Controller {
this.sidebarContext = sidebarContext;
this.universe.sidebarContext = sidebarContext;
if (this.router.currentRouteName === 'console.home' || this.router.currentRouteName === 'console.extensions') {
if (this.hiddenSidebarRoutes.includes(this.router.currentRouteName)) {
this.sidebarContext.hideNow();
this.sidebarToggleEnabled = false;
}
}

View File

@@ -2,5 +2,10 @@ import Controller from '@ember/controller';
import { inject as service } from '@ember/service';
export default class ConsoleAccountController extends Controller {
/**
* Inject the `universe` service.
*
* @memberof ConsoleAdminController
*/
@service universe;
}

View File

@@ -2,5 +2,10 @@ import Controller from '@ember/controller';
import { inject as service } from '@ember/service';
export default class ConsoleAdminController extends Controller {
/**
* Inject the `universe` service.
*
* @memberof ConsoleAdminController
*/
@service universe;
}

View File

@@ -2,6 +2,7 @@ import Controller from '@ember/controller';
import { tracked } from '@glimmer/tracking';
import { inject as service } from '@ember/service';
import { action } from '@ember/object';
import { isArray } from '@ember/array';
export default class ConsoleAdminBrandingController extends Controller {
/**
@@ -51,13 +52,43 @@ export default class ConsoleAdminBrandingController extends Controller {
}
/**
* Unset a branding settings
* Unsets the value of a given key or array of keys on the model.
*
* @param {String} key
* @action
* @param {string | string[]} key - The key or keys to unset on the model.
* @param {*} [newValue=null] - The new value to set for the given key or keys. Defaults to null.
* @memberof ConsoleAdminBrandingController
*/
@action unset(key, newValue = null) {
this.model[key] = newValue;
if (isArray(key)) {
return key.forEach((k) => this.unset(k, undefined));
}
this.model.set(key, newValue);
}
/**
* Unsets the icon properties on the model.
*
* @action
* @returns {void} - No return value.
* @memberof ConsoleAdminBrandingController
*/
@action unsetIcon() {
this.unset(['icon_uuid', 'icon_url']);
this.model.set('icon_url', '/images/icon.png');
}
/**
* Unsets the logo properties on the model.
*
* @action
* @returns {void} - No return value.
* @memberof ConsoleAdminBrandingController
*/
@action unsetLogo() {
this.unset(['logo_uuid', 'logo_url']);
this.model.set('logo_url', '/images/fleetbase-logo-svg.svg');
}
/**
@@ -73,6 +104,16 @@ export default class ConsoleAdminBrandingController extends Controller {
.save()
.then(() => {
this.notifications.success('Branding settings saved.');
// if logo url is null
if (this.model.logo_url === null) {
this.model.set('logo_url', '/images/fleetbase-logo-svg.svg');
}
// if icon url is null
if (this.model.icon_url === null) {
this.model.set('icon_url', '/images/icon.png');
}
})
.finally(() => {
this.isLoading = false;

View File

@@ -0,0 +1,136 @@
import Controller from '@ember/controller';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
import { inject as service } from '@ember/service';
import createNotificationKey from '../../../utils/create-notification-key';
export default class ConsoleAdminNotificationsController extends Controller {
/**
* Inject the notifications service.
*
* @memberof ConsoleAdminNotificationsController
*/
@service notifications;
/**
* Inject the fetch service.
*
* @memberof ConsoleAdminNotificationsController
*/
@service fetch;
/**
* The notification settings value JSON.
*
* @memberof ConsoleAdminNotificationsController
* @var {Object}
*/
@tracked notificationSettings = {};
/**
* Notification transport methods enabled.
*
* @memberof ConsoleAdminNotificationsController
* @var {Array}
*/
@tracked notificationTransportMethods = ['email', 'sms'];
/**
* Tracked property for the loading state
*
* @memberof ConsoleAdminNotificationsController
* @var {Boolean}
*/
@tracked isLoading = false;
/**
* Creates an instance of ConsoleAdminNotificationsController.
* @memberof ConsoleAdminNotificationsController
*/
constructor() {
super(...arguments);
this.getSettings();
}
/**
* Selectes notifiables for settings.
*
* @param {Object} notification
* @param {Array} notifiables
* @memberof ConsoleAdminNotificationsController
*/
@action onSelectNotifiable(notification, notifiables) {
const notificationKey = createNotificationKey(notification.definition, notification.name);
const _notificationSettings = { ...this.notificationSettings };
if (!_notificationSettings[notificationKey]) {
_notificationSettings[notificationKey] = {};
}
_notificationSettings[notificationKey].notifiables = notifiables;
_notificationSettings[notificationKey].definition = notification.definition;
_notificationSettings[notificationKey].via = notifiables.map((notifiable) => {
return {
identifier: notifiable.value,
methods: this.notificationTransportMethods,
};
});
this.mutateNotificationSettings(_notificationSettings);
}
/**
* Mutates the notification settings property.
*
* @param {Object} [_notificationSettings={}]
* @memberof ConsoleAdminNotificationsController
*/
mutateNotificationSettings(_notificationSettings = {}) {
this.notificationSettings = {
...this.notificationSettings,
..._notificationSettings,
};
}
/**
* Save notification settings to the server.
*
* @action
* @method saveSettings
* @returns {Promise}
* @memberof ConsoleAdminNotificationsController
*/
@action saveSettings() {
const { notificationSettings } = this;
this.isLoading = true;
return this.fetch
.post('notifications/save-settings', { notificationSettings })
.then(() => {
this.notifications.success('Notification settings successfully saved.');
})
.catch((error) => {
this.notifications.serverError(error);
})
.finally(() => {
this.isLoading = false;
});
}
/**
* Fetches and updates notification settings asynchronously.
*
* @returns {Promise<void>} A promise for successful retrieval and update, or an error on failure.
*/
getSettings() {
return this.fetch
.get('notifications/get-settings')
.then(({ notificationSettings }) => {
this.notificationSettings = notificationSettings;
})
.catch((error) => {
this.notifications.serverError(error);
});
}
}

View File

@@ -0,0 +1,188 @@
import Controller from '@ember/controller';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
import { inject as service } from '@ember/service';
/**
* Controller for managing notifications.
*/
export default class NotificationsController extends Controller {
/**
* Inject the `socket` service
*
* @memberof NotificationsController
*/
@service socket;
/**
* Inject the `store` service
*
* @memberof NotificationsController
*/
@service store;
/**
* Inject the `fetch` service
*
* @memberof NotificationsController
*/
@service fetch;
/**
* Inject the `notifications` service
*
* @memberof NotificationsController
*/
@service notifications;
/**
* Inject the `universe` service
*
* @memberof NotificationsController
*/
@service universe;
/**
* Inject the `router` service
*
* @memberof NotificationsController
*/
@service router;
/**
* Queryable parameters for this controller's model
*
* @var {Array}
*/
queryParams = ['page', 'limit', 'sort', 'query', 'created_at'];
/**
* The current page of data being viewed
*
* @var {Integer}
*/
@tracked page = 1;
/**
* The maximum number of items to show per page
*
* @var {Integer}
*/
@tracked limit = 20;
/**
* The param to sort the data on, the param with prepended `-` is descending
*
* @var {String}
*/
@tracked sort = '-created_at';
/**
* The selected notifications.
*
* @tracked
* @var {Array}
* @memberof NotificationsController
*/
@tracked selected = [];
/**
* Creates an instance of NotificationsController.
* @memberof NotificationsController
*/
constructor() {
super(...arguments);
// listen for received notifications
this.universe.on('notification.received', () => {
this.router.refresh();
});
}
/**
* Action to select or deselect a notification.
*
* @param {NotificationModel} notification - The notification to select or deselect.
* @memberof NotificationsController
*/
@action selectNotification(notification) {
if (this.selected.includes(notification)) {
this.selected.removeObject(notification);
} else {
this.selected.pushObject(notification);
}
}
/**
* Action to delete selected notifications.
*
* @memberof NotificationsController
*/
@action delete() {
return this.fetch
.delete('notifications/bulk-delete', {
notifications: this.selected.map(({ id }) => id),
})
.then(() => {
this.notifications.success(`${this.selected.length} notifications deleted`);
this.universe.trigger('notifications.deleted', [...this.selected]);
this.selected.clear();
return this.router.refresh();
})
.catch((error) => {
this.notifications.serverError(error);
});
}
/**
* Action to mark selected notifications as read.
*
* @memberof NotificationsController
*/
@action read() {
const unreadSelectedNotifications = this.selected.filter((notification) => notification.unread);
return this.fetch
.put('notifications/mark-as-read', {
notifications: unreadSelectedNotifications.map(({ id }) => id),
})
.then(() => {
this.notifications.success(`${unreadSelectedNotifications.length} notifications marked as read`);
this.universe.trigger('notifications.read', [...unreadSelectedNotifications]);
this.selected.clear();
return this.router.refresh();
})
.catch((error) => {
this.notifications.serverError(error);
});
}
/**
* Action to select all notifications.
*
* @memberof NotificationsController
*/
@action selectAll() {
if (this.selected.length === this.model.length) {
this.selected.clear();
} else {
this.selected = this.model.toArray();
}
}
/**
* Action to mark a notification as read.
*
* @param {NotificationModel} notification
* @return {Promise}
* @memberof NotificationsController
*/
@action markNotificationAsRead(notification) {
return notification.markAsRead().then(() => {
this.notifications.info('Notification marked as read.');
this.universe.trigger('notifications.read', [notification]);
});
}
}

View File

@@ -2,5 +2,10 @@ import Controller from '@ember/controller';
import { inject as service } from '@ember/service';
export default class ConsoleSettingsController extends Controller {
/**
* INject the `universe` service
*
* @memberof ConsoleSettingsController
*/
@service universe;
}

View File

@@ -7,6 +7,7 @@ import { task } from 'ember-concurrency-decorators';
export default class InstallController extends Controller {
@service fetch;
@service notifications;
@service router;
@tracked error;
@tracked steps = [
{ task: 'createdb', name: 'Create Database', status: 'pending' },
@@ -46,7 +47,7 @@ export default class InstallController extends Controller {
if (isCompleted) {
this.notifications.success('Install completed successfully!');
return this.transitionToRoute('onboard');
return this.router.transitionTo('onboard');
}
}

View File

@@ -8,6 +8,7 @@ export default class InviteForUserController extends Controller {
@service session;
@service notifications;
@service modalsManager;
@service router;
@tracked code;
@tracked isLoading;
@@ -24,7 +25,7 @@ export default class InviteForUserController extends Controller {
this.isLoading = false;
return this.transitionToRoute('console').then(() => {
return this.router.transitionTo('console').then(() => {
if (response.needs_password && response.needs_password === true) {
this.setPassword();
}

View File

@@ -21,6 +21,13 @@ export default class OnboardIndexController extends Controller {
*/
@service session;
/**
* Inject the `router` service
*
* @memberof OnboardIndexController
*/
@service router;
/**
* Inject the `notifications` service
*
@@ -122,12 +129,12 @@ export default class OnboardIndexController extends Controller {
this.session.isOnboarding().manuallyAuthenticate(response.token);
if (response.skipVerification === true) {
return this.transitionToRoute('console').then(() => {
return this.router.transitionTo('console').then(() => {
this.notifications.success('Welcome to Fleetbase!');
});
}
return this.transitionToRoute('onboard.verify-email', { queryParams: { hello: response.session } });
return this.router.transitionTo('onboard.verify-email', { queryParams: { hello: response.session } });
}
})
.catch((error) => {

View File

@@ -34,6 +34,14 @@ export default class OnboardVerifyEmailController extends Controller {
*/
@service currentUser;
/**
* Inject the `router` service
*
* @memberof OnboardIndexController
*/
@service router;
/**
* The session paramerer.
*
@@ -139,7 +147,7 @@ export default class OnboardVerifyEmailController extends Controller {
this.notifications.success('Email successfully verified!');
this.notifications.info('Welcome to Fleetbase!');
return this.transitionToRoute('console');
return this.router.transitionTo('console');
}
})
.catch((error) => {

View File

@@ -0,0 +1,6 @@
import { helper } from '@ember/component/helper';
import createNotificationKey from '../utils/create-notification-key';
export default helper(function getNotificationKey([definition, name]) {
return createNotificationKey(definition, name);
});

View File

@@ -10,7 +10,7 @@ export default class Company extends Model {
@attr('string') owner_uuid;
@attr('string') logo_uuid;
@attr('string') backdrop_uuid;
@attr('string') address_uuid;
@attr('string') place_uuid;
/** @relationships */
@belongsTo('file') logo;

View File

@@ -0,0 +1,53 @@
import Model, { attr } from '@ember-data/model';
import { computed } from '@ember/object';
import { format, formatDistanceToNow } from 'date-fns';
export default class NotificationModel extends Model {
@attr('string') notifiable_id;
@attr('string') notifiable_type;
/** @attributes */
@attr('string') type;
@attr('raw') data;
@attr('raw') meta;
/** @dates */
@attr('date') read_at;
@attr('date') created_at;
/** @computed */
@computed('created_at') get createdAgo() {
return formatDistanceToNow(this.created_at);
}
@computed('created_at') get createdAt() {
return format(this.created_at, 'PPP p');
}
@computed('read_at') get readAt() {
return format(this.read_at, 'PPP p');
}
@computed('read_at') get isRead() {
return this.read_at instanceof Date;
}
@computed('read_at') get read() {
return this.read_at instanceof Date;
}
@computed('isRead') get unread() {
return !this.get('isRead');
}
/** @actions */
markAsRead() {
if (this.isRead) {
return;
}
this.set('read_at', new Date());
return this.save();
}
}

View File

@@ -22,6 +22,7 @@ Router.map(function () {
this.route('console', { path: '/' }, function () {
this.route('home', { path: '/' });
this.route('extensions');
this.route('notifications');
this.route('account', function () {
this.route('virtual', { path: '/:slug/:view' });
});
@@ -41,9 +42,15 @@ Router.map(function () {
this.route('socket');
});
this.route('branding');
this.route('notifications');
this.route('virtual', { path: '/:slug/:view' });
});
this.mount('@fleetbase/billing-engine', {
as: 'billing',
path: 'billing'
});
this.mount('@fleetbase/dev-engine', {
as: 'developers',
path: 'developers'

View File

@@ -11,6 +11,7 @@ export default class ApplicationRoute extends Route {
@service urlSearchParams;
@service modalsManager;
@service intl;
@service router;
@tracked defaultTheme;
/**
@@ -27,11 +28,11 @@ export default class ApplicationRoute extends Route {
this.defaultTheme = defaultTheme;
if (shouldInstall) {
return this.transitionTo('install');
return this.router.transitionTo('install');
}
if (shouldOnboard) {
return this.transitionTo('onboard');
return this.router.transitionTo('onboard');
}
}
@@ -49,7 +50,7 @@ export default class ApplicationRoute extends Route {
const shift = this.urlSearchParams.get('shift');
if (isAuthenticated && shift) {
return this.transitionTo(pathToRoute(shift));
return this.router.transitionTo(pathToRoute(shift));
}
}

View File

@@ -4,11 +4,12 @@ import { inject as service } from '@ember/service';
export default class ConsoleAdminRoute extends Route {
@service currentUser;
@service notifications;
@service router;
beforeModel() {
// USER MUST BE ADMIN
if (!this.currentUser.user.is_admin) {
return this.transitionTo('console').then(() => {
return this.router.transitionTo('console').then(() => {
this.notifications.error('You do not have authorization to access admin!');
});
}

View File

@@ -0,0 +1,20 @@
import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';
import { hash } from 'rsvp';
import groupBy from '@fleetbase/ember-core/utils/group-by';
export default class ConsoleAdminNotificationsRoute extends Route {
@service fetch;
model() {
return hash({
registry: this.fetch.get('notifications/registry'),
notifiables: this.fetch.get('notifications/notifiables'),
});
}
setupController(controller, { registry, notifiables }) {
controller.groupedNotifications = groupBy(registry, 'package');
controller.notifiables = notifiables;
}
}

View File

@@ -0,0 +1,27 @@
import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';
/**
* Route for managing console notifications.
*/
export default class ConsoleNotificationsRoute extends Route {
@service store;
queryParams = {
page: { refreshModel: true },
limit: { refreshModel: true },
sort: { refreshModel: true },
query: { refreshModel: true },
created_at: { refreshModel: true },
};
/**
* Fetch the model data based on the specified parameters.
*
* @param {Object} params - Query parameters for fetching notifications.
* @returns {Promise} - A promise that resolves with the notification data.
*/
model(params = {}) {
return this.store.query('notification', params);
}
}

View File

@@ -0,0 +1,5 @@
import ApplicationSerializer from '@fleetbase/ember-core/serializers/application';
export default class NotificationSerializer extends ApplicationSerializer {
primaryKey = 'id';
}

View File

@@ -1,4 +1,4 @@
@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';
@import 'inter-ui/inter.css';
@import 'inter-ui/inter.css';

View File

@@ -1,6 +1,6 @@
{{page-title "Console"}}
<Layout::Container>
<Layout::Header @brand={{@model}} @user={{this.user}} @organizations={{this.organizations}} @menuItems={{this.universe.headerMenuItems}} @extensions={{this.extensions}} @onAction={{this.onAction}} />
<Layout::Header @brand={{@model}} @user={{this.user}} @organizations={{this.organizations}} @menuItems={{this.universe.headerMenuItems}} @extensions={{this.extensions}} @onAction={{this.onAction}} @showSidebarToggle={{true}} @sidebarToggleEnabled={{this.sidebarToggleEnabled}} @onSidebarToggle={{this.onSidebarToggle}} />
<Layout::Main>
<Layout::Sidebar @onSetup={{this.setSidebarContext}}>
<div class="next-sidebar-content-inner">

View File

@@ -3,6 +3,7 @@
<EmberWormhole @to="sidebar-menu-items">
<Layout::Sidebar::Item @route="console.admin.index" @icon="rectangle-list">Overview</Layout::Sidebar::Item>
<Layout::Sidebar::Item @route="console.admin.branding" @icon="palette">Branding</Layout::Sidebar::Item>
<Layout::Sidebar::Item @route="console.admin.notifications" @icon="bell">Notifications</Layout::Sidebar::Item>
{{#each this.universe.adminMenuItems as |menuItem|}}
<Layout::Sidebar::Item @onClick={{fn this.universe.transitionMenuItem "console.admin.virtual" menuItem}} @item={{menuItem}} @icon={{menuItem.icon}}>{{menuItem.title}}</Layout::Sidebar::Item>
{{/each}}

View File

@@ -1,5 +1,7 @@
{{page-title "Branding"}}
<Layout::Section::Header @title="Branding" />
<Layout::Section::Header @title="Branding">
<Button @type="primary" @size="sm" @icon="save" @text="Save Changes" @onClick={{this.save}} @disabled={{this.isLoading}} @isLoading={{this.isLoading}} />
</Layout::Section::Header>
<Layout::Section::Body class="overflow-y-scroll h-full">
<div class="container mx-auto h-screen" {{increase-height-by 300}}>
@@ -28,7 +30,7 @@
</a>
</FileUpload>
</div>
<a href="javascript:;" class="text-danger text-xs mt-1" {{on "click" (fn this.unset "icon_url" "/images/icon.png")}}>Reset to default</a>
<a href="javascript:;" class="text-danger text-xs mt-1" {{on "click" this.unsetIcon}}>Reset to default</a>
</div>
<div class="input-group">
<label>Logo</label>
@@ -52,17 +54,13 @@
</a>
</FileUpload>
</div>
<a href="javascript:;" class="text-danger text-xs mt-1" {{on "click" (fn this.unset "logo_url" "/images/fleetbase-logo-svg.svg")}}>Reset to default</a>
<a href="javascript:;" class="text-danger text-xs mt-1" {{on "click" this.unsetLogo}}>Reset to default</a>
</div>
<InputGroup @name="Default Theme">
<Select @value={{@model.default_theme}} @onSelect={{this.setTheme}} @options={{this.themeOptions}} />
</InputGroup>
</form>
</ContentPanel>
<div class="mt-3 flex items-center justify-end">
<Button @type="primary" @size="lg" @icon="save" @text="Save Changes" @onClick={{this.save}} @disabled={{this.isLoading}} @isLoading={{this.isLoading}} />
</div>
</div>
</div>
</Layout::Section::Body>

View File

@@ -0,0 +1,32 @@
{{page-title "Notifications"}}
<Layout::Section::Header @title="Notifications">
<Button @type="primary" @size="sm" @icon="save" @text="Save Changes" @onClick={{this.saveSettings}} @disabled={{this.isLoading}} @isLoading={{this.isLoading}} />
</Layout::Section::Header>
<Layout::Section::Body class="overflow-y-scroll h-full">
<div class="container mx-auto h-screen" {{increase-height-by 1200}}>
<div class="max-w-3xl my-10 mx-auto space-y-4">
{{#each-in this.groupedNotifications as |groupName notifications|}}
<ContentPanel @title={{concat (smart-humanize groupName) " Notification Settings"}} @open={{true}} @pad={{true}} @panelBodyClass="bg-white dark:bg-gray-800">
{{#each notifications as |notification|}}
<InputGroup @name={{notification.name}} @helpText={{notification.description}}>
<div class="fleetbase-model-select fleetbase-power-select ember-model-select">
<PowerSelectMultiple
@searchEnabled={{true}}
@options={{this.notifiables}}
@selected={{get this.notificationSettings (concat (get-notification-key notification.definition notification.name) ".notifiables")}}
@onChange={{fn this.onSelectNotifiable notification}}
@placeholder="Select notifiables..."
@triggerClass="form-select form-input form-input-sm flex-1"
as |notifiable|
>
{{notifiable.label}}
</PowerSelectMultiple>
</div>
</InputGroup>
{{/each}}
</ContentPanel>
{{/each-in}}
</div>
</div>
</Layout::Section::Body>

View File

@@ -1,6 +1,6 @@
{{page-title "Dashboard"}}
<Layout::Section::Body class="overflow-y-scroll h-full pt-6">
<div class="container mx-auto h-screen space-y-4" {{increase-height-by 300}}>
<div class="console-home-container mx-auto h-screen space-y-4" {{increase-height-by 300}}>
<div class="grid grid-cols-1 md:grid-cols-12 gap-4">
<GithubCard class="lg:col-span-4" />
<FleetbaseBlog class="lg:col-span-8" />

View File

@@ -0,0 +1,51 @@
<Layout::Section::Header @title="Notifications">
<Button @icon="check-square" @type="default" @text="Select All" {{on "click" this.selectAll}} class="mr-2" />
<Button @icon="envelope" @type="primary" @text="Mark as Read" {{on "click" this.read}} class="mr-2" />
<Button @icon="trash" @type="danger" @text="Delete" {{on "click" this.delete}} />
</Layout::Section::Header>
<Layout::Section::Body class="h-full w-full">
<div class="max-h-[calc(100vh-10rem)] h-full w-full overflow-y-scroll">
<div class="h-full w-full">
{{#each @model as |notification|}}
<div class="flex flex-row justify-between px-4 py-3 text-black dark:text-white border-b dark:border-gray-800 border-gray-200 text-sm hover:opacity-60 {{if notification.read_at 'bg-gray-100 dark:bg-gray-900' 'bg-white dark:bg-gray-800'}}">
<div class="flex flex-row flex-1">
<div class="flex items-center justify-center mr-6">
<Checkbox @value={{includes notification this.selected}} @onToggle={{fn this.selectNotification notification}} />
</div>
<a href="javascript:;" class="flex-1 flex flex-row" {{on "click" (fn this.markNotificationAsRead notification)}}>
<div class="mr-4 flex items-center justify-center">
{{#if notification.read_at}}
<FaIcon @icon="envelope-open" class="text-gray-200" />
{{else}}
<FaIcon @icon="envelope" class="text-gray-200" />
{{/if}}
</div>
<div class="flex flex-col">
<div class="flex flex-row space-x-2">
<h1 class="text-sm font-semibold antialiased leading-4">{{notification.data.subject}}</h1>
<div class="text-xs antialiased text-gray-900 dark:text-gray-200">- {{notification.data.message}}</div>
</div>
<div class="text-gray-300 text-xs antialiased mt-1">Received: {{notification.createdAgo}}</div>
</div>
</a>
<div>
<FaIcon @icon="clock" class="text-gray-400 dark:text-gray-700" @size="sm" />
<span class="text-gray-400 dark:text-gray-600 text-xs">{{notification.createdAt}}</span>
</div>
</div>
</div>
{{else}}
<div class="flex items-center justify-center h-full w-full">
<p class="text-base text-gray-800 dark:text-gray-300 italic">No notifications to display.</p>
</div>
{{/each}}
</div>
</div>
</Layout::Section::Body>
<div class="fixed bottom-0 w-full">
<Layout::Section::Footer>
<Pagination @meta={{@model.meta}} @currentPage={{this.page}} @onPageChange={{fn (mut this.page)}} @tfootVerticalOffset="53" @tfootVerticalOffsetElements=".next-view-section-subheader" />
</Layout::Section::Footer>
</div>

View File

@@ -0,0 +1,8 @@
import { camelize } from '@ember/string';
export default function createNotificationKey(definition, name) {
const withoutSlashes = definition.replace(/[\W_]+/g, '');
const key = `${camelize(withoutSlashes)}__${camelize(name)}`;
return key;
}

View File

@@ -3,7 +3,7 @@
"packages": [
{
"name": "ember-cli",
"version": "4.6.0",
"version": "5.4.1",
"blueprints": [
{
"name": "app",

View File

@@ -4,12 +4,13 @@ const getenv = require('./utils/getenv');
const fixApiHost = require('./utils/fix-api-host');
module.exports = function (environment) {
let ENV = {
const ENV = {
modulePrefix: '@fleetbase/console',
environment,
rootURL: '/',
locationType: 'history',
EmberENV: {
EXTEND_PROTOTYPES: true,
FEATURES: {
// Here you can enable experimental features on an ember canary build
// e.g. EMBER_NATIVE_DECORATOR_SUPPORT: true
@@ -21,7 +22,7 @@ module.exports = function (environment) {
},
API: {
host: fixApiHost(getenv('API_HOST'), getenv('API_SECURE')),
host: fixApiHost(getenv('API_HOST'), toBoolean(getenv('API_SECURE'))),
namespace: getenv('API_NAMESPACE', 'int/v1'),
},
@@ -43,7 +44,8 @@ module.exports = function (environment) {
driverImage: getenv('DEFAULT_DRIVER_IMAGE', 'https://s3.ap-southeast-1.amazonaws.com/flb-assets/static/no-avatar.png'),
userImage: getenv('DEFAULT_USER_IMAGE', 'https://s3.ap-southeast-1.amazonaws.com/flb-assets/static/no-avatar.png'),
contactImage: getenv('DEFAULT_CONTACT_IMAGE', 'https://s3.ap-southeast-1.amazonaws.com/flb-assets/static/no-avatar.png'),
vehicleImage: getenv('DEFAULT_VEHICLE_IMAGE', 'https://flb-assets.s3.ap-southeast-1.amazonaws.com/static/vehicle-icons/light_commercial_van.svg'),
vendorImage: getenv('DEFAULT_VENDOR_IMAGE', 'https://s3.ap-southeast-1.amazonaws.com/flb-assets/static/no-avatar.png'),
vehicleImage: getenv('DEFAULT_VEHICLE_IMAGE', 'https://s3.ap-southeast-1.amazonaws.com/flb-assets/static/vehicle-placeholder.png'),
vehicleAvatar: getenv('DEFAUL_VEHICLE_AVATAR', 'https://flb-assets.s3-ap-southeast-1.amazonaws.com/static/vehicle-icons/mini_bus.svg'),
},

View File

@@ -13,7 +13,7 @@ const autoprefixer = require('autoprefixer');
const tailwind = require('tailwindcss');
module.exports = function (defaults) {
let app = new EmberApp(defaults, {
const app = new EmberApp(defaults, {
storeConfigInMeta: false,
fingerprint: {
@@ -43,7 +43,7 @@ module.exports = function (defaults) {
postcssMixins,
postcssPresetEnv({ stage: 1 }),
postcssEach,
tailwind('./tailwind.js'),
tailwind('./tailwind.config.js'),
autoprefixer,
],
},

View File

@@ -0,0 +1,8 @@
API_HOST=http://localhost:8000
API_NAMESPACE=int/v1
SOCKETCLUSTER_PATH=/socketcluster/
SOCKETCLUSTER_HOST=localhost
SOCKETCLUSTER_SECURE=false
SOCKETCLUSTER_PORT=38000
OSRM_HOST=https://bundle.routing.fleetbase.io
OSRM_SERVERS=https://canada.routing.fleetbase.io,https://us.routing.fleetbase.io

View File

@@ -0,0 +1,9 @@
API_HOST=
API_NAMESPACE=int/v1
API_SECURE=true
SOCKETCLUSTER_PATH=/socketcluster/
SOCKETCLUSTER_HOST=
SOCKETCLUSTER_SECURE=true
SOCKETCLUSTER_PORT=38000
OSRM_HOST=https://bundle.routing.fleetbase.io
OSRM_SERVERS=https://canada.routing.fleetbase.io,https://us.routing.fleetbase.io

View File

@@ -1,11 +1,11 @@
{
"name": "@fleetbase/console",
"version": "0.2.5",
"version": "0.3.3",
"private": true,
"description": "Fleetbase Console",
"repository": "",
"repository": "https://github.com/fleetbase/fleetbase",
"license": "MIT",
"author": "",
"author": "Fleetbase Pte Ltd <hello@fleetbase.io>",
"directories": {
"doc": "doc",
"test": "tests"
@@ -13,37 +13,40 @@
"scripts": {
"prebuild": "node prebuild.js",
"build": "pnpm run prebuild && ember build --environment=production",
"lint": "npm-run-all --aggregate-output --continue-on-error --parallel \"lint:!(fix)\"",
"lint:fix": "npm-run-all --aggregate-output --continue-on-error --parallel lint:*:fix",
"lint": "concurrently \"npm:lint:*(!fix)\" --names \"lint:\"",
"lint:css": "stylelint \"**/*.css\"",
"lint:css:fix": "concurrently \"npm:lint:css -- --fix\"",
"lint:fix": "concurrently \"npm:lint:*:fix\" --names \"fix:\"",
"lint:hbs": "ember-template-lint .",
"lint:hbs:fix": "ember-template-lint . --fix",
"lint:js": "eslint . --cache",
"lint:js:fix": "eslint . --fix",
"start": "pnpm run prebuild && ember serve",
"test": "npm-run-all lint test:*",
"test": "concurrently \"npm:lint\" \"npm:test:*\" --names \"lint,test:\"",
"test:ember": "ember test"
},
"dependencies": {
"@ember/legacy-built-in-components": "^0.4.1",
"@fleetbase/ember-core": "^0.1.4",
"@fleetbase/ember-ui": "^0.2.0",
"@fleetbase/dev-engine": "^0.1.8",
"@fleetbase/iam-engine": "^0.0.6",
"@fleetbase/fleetops-engine": "^0.2.8",
"@fleetbase/fleetops-data": "^0.1.1",
"@fleetbase/storefront-engine": "^0.2.0",
"@fleetbase/ember-core": "^0.1.9",
"@fleetbase/ember-ui": "^0.2.8",
"@fleetbase/storefront-engine": "^0.2.5",
"@fleetbase/fleetops-engine": "^0.3.7",
"@fleetbase/fleetops-data": "^0.1.6",
"@fleetbase/dev-engine": "^0.2.0",
"@fleetbase/iam-engine": "^0.0.8",
"@fleetbase/billing-engine": "^0.0.5",
"@fleetbase/leaflet-routing-machine": "^3.2.16",
"@ember/legacy-built-in-components": "^0.4.1",
"@fortawesome/ember-fontawesome": "^0.4.1",
"ember-intl": "^6.0.0-beta.6",
"ember-changeset": "^4.1.2",
"ember-changeset-validations": "^4.1.1",
"ember-composable-helpers": "^5.0.0",
"ember-concurrency": "^3.0.0",
"ember-concurrency-decorators": "^2.0.3",
"ember-intl": "6.3.2",
"ember-math-helpers": "^2.18.2",
"ember-power-select": "^6.0.1",
"ember-prism": "^0.13.0",
"ember-radio-button": "^3.0.0-beta.1",
"ember-radio-button": "3.0.0-beta.1",
"ember-tag-input": "^3.1.0",
"fleetbase-extensions-indexer": "^0.0.4",
"postcss-at-rules-variables": "^0.3.0",
@@ -51,8 +54,12 @@
"postcss-nth-list": "^1.0.2"
},
"devDependencies": {
"@babel/core": "^7.23.2",
"@babel/eslint-parser": "^7.22.15",
"@babel/plugin-proposal-decorators": "^7.23.2",
"@ember/optional-features": "^2.0.0",
"@ember/test-helpers": "^2.8.1",
"@ember/string": "^3.1.1",
"@ember/test-helpers": "^3.2.0",
"@fortawesome/fontawesome-svg-core": "^6.4.0",
"@fortawesome/free-brands-svg-icons": "^6.4.0",
"@fortawesome/free-solid-svg-icons": "^6.4.0",
@@ -60,49 +67,49 @@
"@glimmer/tracking": "^1.1.2",
"@tailwindcss/forms": "^0.5.3",
"autoprefixer": "^10.4.8",
"babel-eslint": "^10.1.0",
"broccoli-asset-rev": "^3.0.0",
"broccoli-funnel": "^3.0.8",
"concurrently": "^8.2.2",
"date-fns": "^2.30.0",
"dragula": "^3.7.3",
"ember-auto-import": "^2.4.2",
"ember-cli": "~4.6.0",
"ember-cli-app-version": "^5.0.0",
"ember-cli-babel": "^7.26.11",
"ember-cli-dependency-checker": "^3.3.1",
"ember-auto-import": "^2.6.3",
"ember-cli": "~5.4.1",
"ember-cli-app-version": "^6.0.1",
"ember-cli-babel": "^8.2.0",
"ember-cli-clean-css": "^3.0.0",
"ember-cli-dependency-checker": "^3.3.2",
"ember-cli-dotenv": "^3.1.0",
"ember-cli-es6-transform": "^1.0.0",
"ember-cli-htmlbars": "^6.1.0",
"ember-cli-htmlbars": "^6.3.0",
"ember-cli-inject-live-reload": "^2.1.0",
"ember-cli-postcss": "^8.2.0",
"ember-cli-sri": "^2.1.1",
"ember-cli-string-helpers": "^6.1.0",
"ember-cli-terser": "^4.0.2",
"ember-data": "^4.6.1",
"ember-data": "^4.12.5",
"ember-engines": "^0.8.23",
"ember-fetch": "^8.1.1",
"ember-fetch": "^8.1.2",
"ember-leaflet": "^5.1.1",
"ember-load-initializers": "^2.1.2",
"ember-page-title": "^7.0.0",
"ember-qunit": "^5.1.5",
"ember-resolver": "^8.0.3",
"ember-modifier": "^4.1.0",
"ember-page-title": "^8.0.0",
"ember-qunit": "^8.0.1",
"ember-resolver": "^11.0.1",
"ember-responsive": "^5.0.0",
"ember-source": "~4.6.0",
"ember-template-lint": "^4.10.1",
"ember-source": "~5.4.0",
"ember-template-lint": "^5.11.2",
"ember-wormhole": "^0.6.0",
"eslint": "^7.32.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-ember": "^11.0.2",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-qunit": "^7.3.1",
"eslint": "^8.52.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-ember": "^11.11.1",
"eslint-plugin-n": "^16.2.0",
"eslint-plugin-prettier": "^5.0.1",
"eslint-plugin-qunit": "^8.0.1",
"fast-glob": "^3.3.0",
"fs": "0.0.1-security",
"inter-ui": "^3.19.3",
"leaflet": "^1.9.4",
"loader.js": "^4.7.0",
"normalize.css": "^8.0.1",
"npm-run-all": "^4.1.5",
"postcss": "^8.4.21",
"postcss-conditionals-renewed": "^1.0.0",
"postcss-each": "^1.1.0",
@@ -110,24 +117,28 @@
"postcss-mixins": "^9.0.4",
"postcss-preset-env": "^7.8.2",
"postcss-simple-vars": "^7.0.0",
"prettier": "^2.7.1",
"qunit": "^2.19.1",
"prettier": "^3.0.3",
"qunit": "^2.20.0",
"qunit-dom": "^2.0.0",
"recast": "^0.23.3",
"stylelint": "^15.11.0",
"stylelint-config-standard": "^34.0.0",
"stylelint-prettier": "^4.0.2",
"tailwindcss": "^3.1.8",
"webpack": "^5.74.0"
"tracked-built-ins": "^3.3.0",
"webpack": "^5.89.0"
},
"engines": {
"node": "14.* || >= 16"
"node": ">= 18"
},
"ember": {
"edition": "octane"
},
"pnpm": {
"overrides": {
"@fleetbase/fleetops-data": "^0.1.1",
"@fleetbase/ember-core": "^0.1.4",
"@fleetbase/ember-ui": "^0.2.0"
"@fleetbase/fleetops-data": "^0.1.6",
"@fleetbase/ember-core": "^0.1.9",
"@fleetbase/ember-ui": "^0.2.8"
}
},
"prettier": {

16804
console/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -5,6 +5,17 @@ const recast = require('recast');
const babelParser = require('recast/parsers/babel');
const builders = recast.types.builders;
function getExtensionMountPath(extensionName) {
let extensionNameSegments = extensionName.split('/');
let mountName = extensionNameSegments[1];
if (typeof mountName !== 'string') {
mountName = extensionNameSegments[0];
}
return mountName.replace('-engine', '');
}
function only(subject, props = []) {
const keys = Object.keys(subject);
const result = {};
@@ -85,8 +96,7 @@ function getRouterFileContents() {
if (functionExpression) {
// Check and add the new engine mounts
extensions.forEach((extension) => {
const mountName = extension.name.split('/')[1];
const mountPath = mountName.replace('-engine', '');
const mountPath = getExtensionMountPath(extension.name);
let route = mountPath;
if (extension.fleetbase && extension.fleetbase.route) {

View File

@@ -22,6 +22,7 @@ Router.map(function () {
this.route('console', { path: '/' }, function () {
this.route('home', { path: '/' });
this.route('extensions');
this.route('notifications');
this.route('account', function () {
this.route('virtual', { path: '/:slug/:view' });
});
@@ -41,6 +42,7 @@ Router.map(function () {
this.route('socket');
});
this.route('branding');
this.route('notifications');
this.route('virtual', { path: '/:slug/:view' });
});
});

View File

@@ -1,6 +1,6 @@
import { setupApplicationTest as upstreamSetupApplicationTest, setupRenderingTest as upstreamSetupRenderingTest, setupTest as upstreamSetupTest } from 'ember-qunit';
// This file exists to provide wrappers around ember-qunit's / ember-mocha's
// This file exists to provide wrappers around ember-qunit's
// test setup functions. This way, you can easily extend the setup that is
// needed per test type.

View File

@@ -0,0 +1,26 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from '@fleetbase/console/tests/helpers';
import { render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
module('Integration | Component | notification-list', function (hooks) {
setupRenderingTest(hooks);
test('it renders', async function (assert) {
// Set any properties with this.set('myProperty', 'value');
// Handle any actions with this.set('myAction', function(val) { ... });
await render(hbs`<NotificationList />`);
assert.dom(this.element).hasText('');
// Template block usage:
await render(hbs`
<NotificationList>
template block text
</NotificationList>
`);
assert.dom(this.element).hasText('template block text');
});
});

View File

@@ -0,0 +1,26 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from '@fleetbase/console/tests/helpers';
import { render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
module('Integration | Component | notifications-list', function (hooks) {
setupRenderingTest(hooks);
test('it renders', async function (assert) {
// Set any properties with this.set('myProperty', 'value');
// Handle any actions with this.set('myAction', function(val) { ... });
await render(hbs`<NotificationsList />`);
assert.dom(this.element).hasText('');
// Template block usage:
await render(hbs`
<NotificationsList>
template block text
</NotificationsList>
`);
assert.dom(this.element).hasText('template block text');
});
});

View File

@@ -0,0 +1,17 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from '@fleetbase/console/tests/helpers';
import { render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
module('Integration | Helper | get-notification-key', function (hooks) {
setupRenderingTest(hooks);
// TODO: Replace this with your real tests.
test('it renders', async function (assert) {
this.set('inputValue', '1234');
await render(hbs`{{get-notification-key this.inputValue}}`);
assert.dom(this.element).hasText('1234');
});
});

View File

@@ -0,0 +1,12 @@
import { module, test } from 'qunit';
import { setupTest } from '@fleetbase/console/tests/helpers';
module('Unit | Controller | console/admin/notifications', function (hooks) {
setupTest(hooks);
// TODO: Replace this with your real tests.
test('it exists', function (assert) {
let controller = this.owner.lookup('controller:console/admin/notifications');
assert.ok(controller);
});
});

View File

@@ -0,0 +1,12 @@
import { module, test } from 'qunit';
import { setupTest } from '@fleetbase/console/tests/helpers';
module('Unit | Controller | console/notifications', function (hooks) {
setupTest(hooks);
// TODO: Replace this with your real tests.
test('it exists', function (assert) {
let controller = this.owner.lookup('controller:console/notifications');
assert.ok(controller);
});
});

View File

@@ -0,0 +1,14 @@
import { module, test } from 'qunit';
import { setupTest } from '@fleetbase/console/tests/helpers';
module('Unit | Model | notification', function (hooks) {
setupTest(hooks);
// Replace this with your real tests.
test('it exists', function (assert) {
let store = this.owner.lookup('service:store');
let model = store.createRecord('notification', {});
assert.ok(model);
});
});

View File

@@ -0,0 +1,11 @@
import { module, test } from 'qunit';
import { setupTest } from '@fleetbase/console/tests/helpers';
module('Unit | Route | console/admin/notifications', function (hooks) {
setupTest(hooks);
test('it exists', function (assert) {
let route = this.owner.lookup('route:console/admin/notifications');
assert.ok(route);
});
});

View File

@@ -0,0 +1,11 @@
import { module, test } from 'qunit';
import { setupTest } from '@fleetbase/console/tests/helpers';
module('Unit | Route | console/notifications', function (hooks) {
setupTest(hooks);
test('it exists', function (assert) {
let route = this.owner.lookup('route:console/notifications');
assert.ok(route);
});
});

View File

@@ -0,0 +1,24 @@
import { module, test } from 'qunit';
import { setupTest } from '@fleetbase/console/tests/helpers';
module('Unit | Serializer | notification', function (hooks) {
setupTest(hooks);
// Replace this with your real tests.
test('it exists', function (assert) {
let store = this.owner.lookup('service:store');
let serializer = store.serializerFor('notification');
assert.ok(serializer);
});
test('it serializes records', function (assert) {
let store = this.owner.lookup('service:store');
let record = store.createRecord('notification', {});
let serializedRecord = record.serialize();
assert.ok(serializedRecord);
});
});

View File

@@ -0,0 +1,10 @@
import createNotificationKey from '@fleetbase/console/utils/create-notification-key';
import { module, test } from 'qunit';
module('Unit | Utility | create-notification-key', function () {
// TODO: Replace this with your real tests.
test('it works', function (assert) {
let result = createNotificationKey();
assert.ok(result);
});
});

View File

@@ -2,6 +2,7 @@
variable "REGISTRY" { default = "" }
variable "VERSION" { default = "latest" }
variable "CACHE" { default = "" }
variable "GCP" { default = false }
group "default" {
targets = ["app", "app-httpd"]
@@ -23,7 +24,7 @@ target "app" {
]
tags = notequal("", REGISTRY) ? formatlist(
"${REGISTRY}:${tgt}-%s",
GCP ? "${REGISTRY}/${tgt}:%s" : "${REGISTRY}:${tgt}-%s",
compact(["latest", VERSION])
) : []
@@ -43,7 +44,7 @@ target "app-httpd" {
]
tags = notequal("", REGISTRY) ? formatlist(
"${REGISTRY}:app-httpd-%s",
GCP ? "${REGISTRY}/app-httpd:%s" : "${REGISTRY}:app-httpd-%s",
compact(["latest", VERSION])
) : []
}

View File

@@ -50,6 +50,7 @@ services:
SESSION_DOMAIN: localhost
BROADCAST_DRIVER: socketcluster
MAIL_FROM_NAME: Fleetbase
APP_NAME: Fleetbase
LOG_CHANNEL: daily
depends_on:
- database

23
infra/helm/.helmignore Normal file
View File

@@ -0,0 +1,23 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/

24
infra/helm/Chart.yaml Normal file
View File

@@ -0,0 +1,24 @@
apiVersion: v2
name: app
description: A Helm chart for Kubernetes
# A chart can be either an 'application' or a 'library' chart.
#
# Application charts are a collection of templates that can be packaged into versioned archives
# to be deployed.
#
# Library charts provide useful utilities or functions for the chart developer. They're included as
# a dependency of application charts to inject those utilities and functions into the rendering
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.1.0
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
appVersion: "1.16.0"

1
infra/helm/mysql.txt Normal file
View File

@@ -0,0 +1 @@
Oyster-Upstart-Acronym6

1
infra/helm/secrets.env Normal file
View File

@@ -0,0 +1 @@
DATABASE_URL=mysql://root:Oyster-Upstart-Acronym6@10.87.32.3/fleetbase

View File

@@ -0,0 +1,3 @@
1. Get the application URL by running these commands:
https://{{ .Values.api_host }}

View File

@@ -0,0 +1,62 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "helm.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "helm.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "helm.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "helm.labels" -}}
helm.sh/chart: {{ include "helm.chart" . }}
{{ include "helm.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "helm.selectorLabels" -}}
app.kubernetes.io/name: {{ include "helm.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Create the name of the service account to use
*/}}
{{- define "helm.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "helm.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,17 @@
apiVersion: networking.gke.io/v1
kind: ManagedCertificate
metadata:
name: managed-cert
spec:
domains:
- {{ .Values.api_host }}
- {{ .Values.socketcluster_host }}
---
apiVersion: networking.gke.io/v1beta1
kind: FrontendConfig
metadata:
name: httpsredirect
spec:
redirectToHttps:
enabled: true
responseCodeName: MOVED_PERMANENTLY_DEFAULT

View File

@@ -0,0 +1,81 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "helm.fullname" . }}
labels:
{{- include "helm.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "helm.selectorLabels" . | nindent 6 }}
template:
metadata:
{{- with .Values.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "helm.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "helm.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: {{ .Chart.Name }}-httpd
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}/app-httpd:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
env:
- name: NGINX_APPLICATION_HOSTNAME
value: localhost
ports:
- name: http
containerPort: {{ .Values.service.port }}
protocol: TCP
livenessProbe:
httpGet:
path: /
port: http
readinessProbe:
httpGet:
path: /
port: http
resources:
{{- toYaml .Values.resources | nindent 12 }}
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}/app:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
envFrom:
- secretRef:
name: infra-provided-secret
env:
- name: CACHE_URL
value: $(REDIS_SERVICE_PORT)/1
- name: SOCKETCLUSTER_PORT
value: "8000"
- name: SOCKETCLUSTER_HOST
value: $(SOCKETCLUSTER_SERVICE_HOST)
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}

View File

@@ -0,0 +1,28 @@
{{- if .Values.autoscaling.enabled }}
apiVersion: autoscaling/v2beta1
kind: HorizontalPodAutoscaler
metadata:
name: {{ include "helm.fullname" . }}
labels:
{{- include "helm.labels" . | nindent 4 }}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ include "helm.fullname" . }}
minReplicas: {{ .Values.autoscaling.minReplicas }}
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
metrics:
{{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
- type: Resource
resource:
name: cpu
targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
{{- end }}
{{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
- type: Resource
resource:
name: memory
targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,60 @@
{{- if .Values.ingress.enabled -}}
{{- $fullName := include "helm.fullname" . -}}
{{- $svcPort := .Values.service.port -}}
{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }}
{{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }}
{{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}}
{{- end }}
{{- end }}
{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}}
apiVersion: networking.k8s.io/v1
{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
apiVersion: networking.k8s.io/v1beta1
{{- else -}}
apiVersion: extensions/v1beta1
{{- end }}
kind: Ingress
metadata:
name: {{ $fullName }}
labels:
{{- include "helm.labels" . | nindent 4 }}
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }}
ingressClassName: {{ .Values.ingress.className }}
{{- end }}
{{- if .Values.ingress.tls }}
tls:
{{- range .Values.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
- host: {{ .Values.api_host }}
http:
paths:
- path: /*
pathType: ImplementationSpecific
backend:
service:
name: fleetbase-app
port:
number: {{ $svcPort }}
- host: {{ .Values.socketcluster_host }}
http:
paths:
- path: /*
pathType: ImplementationSpecific
backend:
service:
name: socketcluster
port:
number: {{ $svcPort }}
{{- end }}

View File

@@ -0,0 +1,33 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis
spec:
replicas: 1
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
containers:
- name: redis
image: redis:6-alpine # Use the Redis Docker image
ports:
- containerPort: 6379
---
apiVersion: v1
kind: Service
metadata:
name: redis-service
spec:
selector:
app: redis
ports:
- protocol: TCP
port: 6379
targetPort: 6379
type: ClusterIP

View File

@@ -0,0 +1,15 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "helm.fullname" . }}
labels:
{{- include "helm.labels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
name: http
selector:
{{- include "helm.selectorLabels" . | nindent 4 }}

View File

@@ -0,0 +1,12 @@
{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "helm.serviceAccountName" . }}
labels:
{{- include "helm.labels" . | nindent 4 }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,36 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: socketcluster
spec:
replicas: 1
selector:
matchLabels:
app: socketcluster
template:
metadata:
labels:
app: socketcluster
spec:
containers:
- name: socketcluster
image: socketcluster/socketcluster:v19.1.2
ports:
- containerPort: 8000
env:
- name: SOCKETCLUSTER_PORT
value: "8000"
---
apiVersion: v1
kind: Service
metadata:
name: socketcluster
spec:
selector:
app: socketcluster
ports:
- protocol: TCP
port: 80
targetPort: 8000
type: ClusterIP

View File

@@ -0,0 +1,15 @@
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "helm.fullname" . }}-test-connection"
labels:
{{- include "helm.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": test
spec:
containers:
- name: wget
image: busybox
command: ['wget']
args: ['{{ include "helm.fullname" . }}:{{ .Values.service.port }}']
restartPolicy: Never

80
infra/helm/values.yaml Normal file
View File

@@ -0,0 +1,80 @@
# Default values for helm.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
replicaCount: 1
image:
repository: OVERRIDE
pullPolicy: IfNotPresent
# Overrides the image tag whose default is the chart appVersion.
tag: "latest"
imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
serviceAccount:
# Specifies whether a service account should be created
create: true
# Annotations to add to the service account
annotations: {}
# The name of the service account to use.
# If not set and create is true, a name is generated using the fullname template
name: ""
podAnnotations: {}
podSecurityContext: {}
# fsGroup: 2000
securityContext: {}
# capabilities:
# drop:
# - ALL
# readOnlyRootFilesystem: true
# runAsNonRoot: true
# runAsUser: 1000
service:
type: ClusterIP
port: 80
ingress:
enabled: true
className: ""
annotations:
kubernetes.io/ingress.class: gce
kubernetes.io/ingress.global-static-ip-name: OVERRIDE
networking.gke.io/managed-certificates: managed-cert
networking.gke.io/v1beta1.FrontendConfig: "httpsredirect"
tls: {}
# - secretName: chart-example-tls
# hosts:
# - chart-example.local
resources: {}
# We usually recommend not to specify default resources and to leave this as a conscious
# choice for the user. This also increases chances charts run on environments with little
# resources, such as Minikube. If you do want to specify resources, uncomment the following
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
# limits:
# cpu: 100m
# memory: 128Mi
# requests:
# cpu: 100m
# memory: 128Mi
autoscaling:
enabled: false
minReplicas: 1
maxReplicas: 100
targetCPUUtilizationPercentage: 80
# targetMemoryUtilizationPercentage: 80
nodeSelector: {}
tolerations: []
affinity: {}

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