mirror of
https://github.com/fleetbase/fleetbase.git
synced 2026-01-05 22:05:50 +00:00
Compare commits
88 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a33c46ecd8 | ||
|
|
3fe1e0a439 | ||
|
|
7ae150137a | ||
|
|
fd1ee4cccc | ||
|
|
ca2bab9261 | ||
|
|
8c0d5f04c2 | ||
|
|
18f813c95b | ||
|
|
ca6004c191 | ||
|
|
cd5a7370b9 | ||
|
|
989312f3f8 | ||
|
|
9fe1af84a7 | ||
|
|
b4ff469c8b | ||
|
|
5d54ffbfda | ||
|
|
445d782fe6 | ||
|
|
4ffc721ecf | ||
|
|
4623910ebf | ||
|
|
af8e30ad7f | ||
|
|
f38d65028e | ||
|
|
eb90b77151 | ||
|
|
536f36487b | ||
|
|
93dc4afc03 | ||
|
|
9fc53de9fe | ||
|
|
bd37005d4b | ||
|
|
f15d509cd1 | ||
|
|
1755c19d6b | ||
|
|
ae300f9004 | ||
|
|
0ad5877271 | ||
|
|
5ffd0461e8 | ||
|
|
50a686ab80 | ||
|
|
1f7937b6ea | ||
|
|
90a0e09321 | ||
|
|
304933af7e | ||
|
|
96e6439d1e | ||
|
|
0ae5f26a63 | ||
|
|
3e90246dfd | ||
|
|
ad055cc4e6 | ||
|
|
a35c0354b1 | ||
|
|
e9bf831ce8 | ||
|
|
2c20db4155 | ||
|
|
801d73215f | ||
|
|
ed1609ccf4 | ||
|
|
82ae99eb84 | ||
|
|
7874edf0f3 | ||
|
|
7281174d12 | ||
|
|
cc4e28cd00 | ||
|
|
bbd1e1bdfe | ||
|
|
4191d6a0bb | ||
|
|
dba2b7afc5 | ||
|
|
a6e6625ae1 | ||
|
|
3777569f22 | ||
|
|
fa5b6700a3 | ||
|
|
6cce6a9db2 | ||
|
|
29bd3ce217 | ||
|
|
39d405cb57 | ||
|
|
9f88c7bc79 | ||
|
|
b3816c394a | ||
|
|
34462c61c4 | ||
|
|
080302eb86 | ||
|
|
b063cf6338 | ||
|
|
b6dca79251 | ||
|
|
c35af4d47a | ||
|
|
2a89659cc3 | ||
|
|
f8ae75d767 | ||
|
|
924448a4d0 | ||
|
|
5426ac374d | ||
|
|
73f56b7958 | ||
|
|
095eb87e14 | ||
|
|
fabc16612b | ||
|
|
3389cba935 | ||
|
|
099ea57c39 | ||
|
|
5492bc7299 | ||
|
|
00b037a385 | ||
|
|
151fea2505 | ||
|
|
08f901d865 | ||
|
|
116873a1ce | ||
|
|
20a1793447 | ||
|
|
72ab83dc7a | ||
|
|
171e365ba6 | ||
|
|
009f2d6e53 | ||
|
|
4a59318feb | ||
|
|
8920039b40 | ||
|
|
cd9be05714 | ||
|
|
fbe35545e9 | ||
|
|
56ab967d7a | ||
|
|
79604c7981 | ||
|
|
312eb1aa6f | ||
|
|
6572a59120 | ||
|
|
fa536c6183 |
170
.github/workflows/eks-cd.yml
vendored
Normal file
170
.github/workflows/eks-cd.yml
vendored
Normal file
@@ -0,0 +1,170 @@
|
||||
name: Fleetbase EKS CI/CD
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["eksdeploy/*"]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
PROJECT: ${{ secrets.PROJECT }}
|
||||
GITHUB_AUTH_KEY: ${{ secrets._GITHUB_AUTH_TOKEN }}
|
||||
|
||||
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
|
||||
|
||||
- name: Configure AWS Credentials
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
with:
|
||||
role-to-assume: ${{ secrets.EKS_DEPLOYER_ROLE }}
|
||||
role-session-name: github
|
||||
aws-region: ${{ secrets.AWS_REGION }}
|
||||
|
||||
- name: Login to Amazon ECR
|
||||
id: login-ecr
|
||||
uses: aws-actions/amazon-ecr-login@v1
|
||||
|
||||
- name: Build and Release
|
||||
uses: docker/bake-action@v2
|
||||
env:
|
||||
REGISTRY: ${{ steps.login-ecr.outputs.registry }}/${{ env.PROJECT }}-${{ env.STACK }}
|
||||
VERSION: ${{ env.VERSION }}
|
||||
GITHUB_AUTH_KEY: ${{ env.GITHUB_AUTH_KEY }}
|
||||
CACHE: type=gha
|
||||
with:
|
||||
push: true
|
||||
files: |
|
||||
./docker-bake.hcl
|
||||
|
||||
- name: Update kube config
|
||||
run: aws eks update-kubeconfig --name ${{ secrets.EKS_CLUSTER_NAME }} --region ${{ secrets.AWS_REGION }}
|
||||
|
||||
- name: Deploy the images 🚀
|
||||
env:
|
||||
REGISTRY: ${{ steps.login-ecr.outputs.registry }}/${{ env.PROJECT }}-${{ env.STACK }}
|
||||
run: |-
|
||||
set -eu
|
||||
# run deploy.sh script before deployments
|
||||
helm upgrade -i ${{ env.PROJECT }} infra/helm -n ${{ env.PROJECT}}-${{ env.STACK }} --set image.repository=${{ env.REGISTRY }} \
|
||||
--set image.tag=${{ env.VERSION }} --set 'api_host=${{ secrets.API_HOST }}' --set 'socketcluster_host=${{ secrets.SOCKETCLUSTER_HOST }}' \
|
||||
--set gcp=false --set 'ingress.annotations.kubernetes\.io/ingress\.class=null' --set 'ingress.annotations.alb\.ingress\.kubernetes\.io/scheme=internet-facing' \
|
||||
--set serviceAccount.name=default --set serviceAccount.create=false --set ingress.className=alb \
|
||||
--set 'ingress.annotations.alb\.ingress\.kubernetes\.io/listen-ports=[{"HTTPS":443}]' \
|
||||
--set service.type=NodePort
|
||||
|
||||
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
|
||||
|
||||
- name: Configure AWS Credentials
|
||||
uses: aws-actions/configure-aws-credentials@v2
|
||||
with:
|
||||
role-to-assume: ${{ secrets.EKS_DEPLOYER_ROLE }}
|
||||
role-session-name: github
|
||||
aws-region: ${{ secrets.AWS_REGION }}
|
||||
|
||||
- name: Get infra-provided configuration
|
||||
run: |
|
||||
set -eu
|
||||
|
||||
wget -O- https://github.com/springload/ssm-parent/releases/download/1.8.0/ssm-parent_1.8.0_linux_amd64.tar.gz | tar xvzf - ssm-parent
|
||||
|
||||
./ssm-parent -n /actions/${{ env.PROJECT }}/${{ env.STACK }}/configuration dotenv /tmp/dotenv.file
|
||||
# remove double quotes and pipe into the env
|
||||
cat /tmp/dotenv.file | sed -e 's/"//g' >> $GITHUB_ENV
|
||||
|
||||
- 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:
|
||||
API_HOST: ${{ secrets.API_HOST }}
|
||||
SOCKETCLUSTER_HOST: ${{ secrets.SOCKETCLUSTER_HOST }}
|
||||
SOCKETCLUSTER_PORT: "443" # it uses common ingress so port 443
|
||||
run: |
|
||||
set -eu
|
||||
|
||||
pnpm build --environment production
|
||||
working-directory: ./console
|
||||
|
||||
- name: Deploy Console 🚀
|
||||
run: |
|
||||
set -u
|
||||
|
||||
DEPLOY_BUCKET=${STATIC_DEPLOY_BUCKET:-${{ env.PROJECT }}-${{ env.STACK }}}
|
||||
# this value will come from the dotenv above
|
||||
echo "Deploying to $DEPLOY_BUCKET"
|
||||
wget -O- https://github.com/bep/s3deploy/releases/download/v2.11.0/s3deploy_2.11.0_linux-amd64.tar.gz | tar xzv -f - s3deploy
|
||||
./s3deploy -region ${AWS_REGION} -source console/dist -bucket ${DEPLOY_BUCKET}
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -15,6 +15,7 @@ api/vendor
|
||||
api/composer.dev.json
|
||||
api/composer-install-dev.sh
|
||||
api/auth.json
|
||||
api/crontab
|
||||
act.sh
|
||||
composer-auth.json
|
||||
docker/database/*
|
||||
|
||||
9
.gitmodules
vendored
9
.gitmodules
vendored
@@ -33,9 +33,6 @@
|
||||
path = packages/fleetops-data
|
||||
url = git@github.com:fleetbase/fleetops-data.git
|
||||
branch = main
|
||||
[submodule "docs/guides"]
|
||||
path = docs/guides
|
||||
url = git@github.com:fleetbase/guides.git
|
||||
[submodule "docs/api-reference"]
|
||||
path = docs/api-reference
|
||||
url = git@github.com:fleetbase/api-reference.git
|
||||
[submodule "docs"]
|
||||
path = docs
|
||||
url = git@github.com:fleetbase/docs.git
|
||||
|
||||
11
COMMERCIAL_LICENSE.md
Normal file
11
COMMERCIAL_LICENSE.md
Normal file
@@ -0,0 +1,11 @@
|
||||
FLEETBASE DUAL LICENSE
|
||||
|
||||
COPYRIGHT (C) 2024 FLEETBASE PTE LTD.
|
||||
|
||||
PERMISSION IS HEREBY GRANTED, FREE OF CHARGE, TO ANY PERSON OBTAINING A COPY OF THIS SOFTWARE AND ASSOCIATED DOCUMENTATION FILES (THE "SOFTWARE"), TO USE THE SOFTWARE FOR NON-COMMERCIAL PURPOSES ONLY. NON-COMMERCIAL PURPOSES INCLUDE INTERNAL OPERATIONS, ACADEMIC RESEARCH, PERSONAL PROJECTS, OR ANY OTHER USE THAT IS NOT INTENDED FOR COMMERCIAL GAIN.
|
||||
|
||||
FOR VERSIONS 0.4.10 ONWARDS, YOU ARE PERMITTED TO USE THE SOFTWARE FOR NON-COMMERCIAL PURPOSES FREE OF CHARGE. HOWEVER, COMMERCIAL USE OF THIS SOFTWARE, INCLUDING BUT NOT LIMITED TO BUILDING SAAS PLATFORMS, OFFERING SERVICES TO THIRD PARTIES, OR INTEGRATING WITH COMMERCIAL PRODUCTS, REQUIRES THE PURCHASE OF A COMMERCIAL LICENSE FROM FLEETBASE PTE LTD.
|
||||
|
||||
FOR INQUIRIES REGARDING COMMERCIAL LICENSING OR ANY OTHER QUESTIONS RELATED TO FLEETBASE, PLEASE CONTACT HELLO@FLEETBASE.IO.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
21
LICENSE
21
LICENSE
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Fleetbase Pte Ltd
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
21
LICENSE.md
Normal file
21
LICENSE.md
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT LICENSE
|
||||
|
||||
COPYRIGHT (C) 2023 FLEETBASE PTE LTD
|
||||
|
||||
PERMISSION IS HEREBY GRANTED, FREE OF CHARGE, TO ANY PERSON OBTAINING A COPY
|
||||
OF THIS SOFTWARE AND ASSOCIATED DOCUMENTATION FILES (THE "SOFTWARE"), TO DEAL
|
||||
IN THE SOFTWARE WITHOUT RESTRICTION, INCLUDING WITHOUT LIMITATION THE RIGHTS
|
||||
TO USE, COPY, MODIFY, MERGE, PUBLISH, DISTRIBUTE, SUBLICENSE, AND/OR SELL
|
||||
COPIES OF THE SOFTWARE, AND TO PERMIT PERSONS TO WHOM THE SOFTWARE IS
|
||||
FURNISHED TO DO SO, SUBJECT TO THE FOLLOWING CONDITIONS:
|
||||
|
||||
THE ABOVE COPYRIGHT NOTICE AND THIS PERMISSION NOTICE SHALL BE INCLUDED IN ALL
|
||||
COPIES OR SUBSTANTIAL PORTIONS OF THE SOFTWARE.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
337
README.md
337
README.md
@@ -1,59 +1,47 @@
|
||||
<div id="hero">
|
||||
<p align="center" dir="auto">
|
||||
<a href="https://fleetbase.io" rel="nofollow">
|
||||
<img src="https://user-images.githubusercontent.com/58805033/191936702-fed04b0f-7966-4041-96d0-95e27bf98248.png" alt="Fleetbase logo" width="600" height="140" style="max-width: 100%;">
|
||||
<img src="https://user-images.githubusercontent.com/58805033/191936702-fed04b0f-7966-4041-96d0-95e27bf98248.png" alt="Fleetbase logo" width="500" height="120" style="max-width: 100%;">
|
||||
</a>
|
||||
</p>
|
||||
<p align="center" dir="auto">
|
||||
Open Source Modular Logistics Platform
|
||||
Modular logistics and supply chain operating system
|
||||
<br>
|
||||
<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://docs.fleetbase.io/" rel="nofollow">Documentation</a>
|
||||
·
|
||||
<a href="https://fleetbase.github.io/api-reference">API Reference</a>
|
||||
·
|
||||
<a href="https://fleetbase.github.io/guides">Guides</a>
|
||||
·
|
||||
<a href="https://github.com/fleetbase/fleetbase/issues">Request a Feature</a>
|
||||
·
|
||||
<a href="https://www.fleetbase.io/blog-2" rel="nofollow">Blog</a>
|
||||
<a href="https://console.fleetbase.io" rel="nofollow">Cloud Version</a>
|
||||
·
|
||||
<a href="https://fleetbase.apichecker.com" target="_api_status" rel="nofollow">API Status</a>
|
||||
·
|
||||
<a href="https://meetings.hubspot.com/shiv-thakker" rel="nofollow">Book a Demo</a>
|
||||
·
|
||||
<a href="https://discord.gg/V7RVWRQ2Wm" target="discord" rel="nofollow">Discord</a>
|
||||
</p>
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
# ⭐️ Overview
|
||||
## What is Fleetbase?
|
||||
|
||||
Fleetbase is an open-source modular platform designed for the efficient management and orchestration of logistics operations. It serves as both a powerful operational tool for businesses and a flexible foundation for developers. The platform's core is built around a collection of "extensions," which create a customizable framework capable of meeting a wide range of supply chain and logistics requirements.
|
||||
|
||||
Each extension in Fleetbase is purposefully engineered to fulfill specific roles within the logistics ecosystem. Users have the freedom to create their own extensions, expanding the platform's ecosystem and ensuring its adaptability to various use cases. This extensible nature keeps Fleetbase at the forefront of addressing diverse logistical and supply chain needs now and in the future.
|
||||
Fleetbase is a modular logistics and supply chain operating system designed to streamline management, planning, optimization, and operational control across various sectors of the supply chain industry.
|
||||
|
||||
<p align="center" dir="auto">
|
||||
<img src="https://github.com/fleetbase/fleetbase/assets/816371/deef79fa-e30c-4ce9-8a04-0dee990ffd9d" alt="Fleetbase Console" width="600" style="max-width: 100%;" />
|
||||
<img src="https://github.com/fleetbase/fleetbase/assets/816371/125348c9-c88a-49fe-b098-9abec9d7dff8" alt="Fleetbase Console" width="1200" style="max-width: 100%;" />
|
||||
</p>
|
||||
|
||||
<div align="center">
|
||||
<a href="https://www.producthunt.com/posts/fleetbase-alpha" target="_producthunt">🚀 We've just announced our Alpha release on Product Hunt! 🚀</a>
|
||||
<p>Check Fleetbase out on ProductHunt, and support with a Upvote!</p>
|
||||
<a href="https://www.producthunt.com/posts/fleetbase-alpha?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-fleetbase-alpha" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=399731&theme=light" alt="Fleetbase (Alpha) - The open source OnFleet alternative | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
||||
</div>
|
||||
|
||||
**Quickstart**
|
||||
|
||||
# 📖 Table of contents
|
||||
```bash
|
||||
git clone git@github.com:fleetbase/fleetbase.git
|
||||
cd fleetbase
|
||||
docker-compose up -d
|
||||
docker exec -ti fleetbase-application-1 bash
|
||||
sh deploy.sh
|
||||
```
|
||||
|
||||
- [What's Included](#-whats-included)
|
||||
- [Getting Started](#-getting-started)
|
||||
- [Use Cases](#-use-cases)
|
||||
- [Installation](#-installation)
|
||||
- [Extensions](#-extensions)
|
||||
## 📖 Table of contents
|
||||
|
||||
- [Features](#-features)
|
||||
- [Install](#-install)
|
||||
- [Apps](#-apps)
|
||||
- [Roadmap](#-roadmap)
|
||||
- [Bugs and Feature Requests](#-bugs-and--feature-requests)
|
||||
@@ -63,211 +51,66 @@ Each extension in Fleetbase is purposefully engineered to fulfill specific roles
|
||||
- [Creators](#-creators)
|
||||
- [License & Copyright](#-license-and-copyright)
|
||||
|
||||
# 📦 What's Included
|
||||
## 📦 Features
|
||||
- **Extensible:** Build installable extensions and additional functionality directly into the OS via modular architecture.
|
||||
- **Developer Friendly:** RESTful API, socket, and webhooks to seamlessly integrate with external systems or develop custom applications.
|
||||
- **Native Apps:** Collection of open-source and native apps designed for operations and customer facing.
|
||||
- **Collaboration:** Dedicated chat and comments system for collaboration across your organization.
|
||||
- **Security:** Secure data encryption, adherence to industry-standard security practices, and a comprehensive dynamic Identity and Access Management (IAM) system.
|
||||
- **Telematics:** Integrate and connect to hardware devices and sensors to provide more feedback and visibility into operations.
|
||||
- **Internationalized:** Translate into multiple languages to accommodate diverse user bases and global operations.
|
||||
- **Framework:** PHP core built around logistics and supply chain abstractions to streamline extension development.
|
||||
- **Dynamic:** Configurable rules, flows and logic to enable automation and customization.
|
||||
- **UI/UX:** Clean, responsive user-friendly interface for efficient management and operations from desktop or mobile.
|
||||
- **Dashboards:** Create custom dashboards and widgets to get full visibility into operations.
|
||||
- **Scalability:** Uninterrupted growth with scalable infrastructure and design, capable of handling increasing data volume and user demand as your business expands.
|
||||
- **Continuous Improvements:** Commitment to continuous improvement, providing regular updates that seamlessly introduce optimizations, new features, and overall enhancements to the OS.
|
||||
- **Open Source:** Deploy it either on-premise or in the cloud according to your organization's needs and preferences.
|
||||
|
||||
Fleetbase is more than just a platform; it's a versatile ecosystem carefully architected to empower developers and businesses alike. Fleetbase comes pre-installed with a few extensions that provide base functionality to get users and businesses started:
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<strong>Console</strong>: Fleetbase's frontend console is built with Ember.js and Ember Engines, offering a modular and extensible design. This design allows the system to easily adapt and scale according to your evolving needs while simplifying the integration of new extensions. By leveraging the console's design, extensions can be seamlessly installed using their respective package managers, reducing complexity and integration time.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Fleetbase API</strong>: Fleetbase's backend API and framework are built with Laravel, providing a robust and flexible infrastructure for extension development and integration. The system efficiently manages complex data structures and transactions while seamlessly incorporating new extensions through package managers. We offer additional packages for developers to create custom extensions, enhancing the flexibility and extensibility of the Fleetbase ecosystem.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Extensions</strong>: Fleetbase is designed to provide immediate utility out-of-the-box. It comes pre-installed with several key extensions
|
||||
<ul>
|
||||
<li><strong>FleetOps</strong>: FleetOps, our comprehensive fleet management extension, caters to all aspects of last-mile operations. Some of it's features include:
|
||||
<ul>
|
||||
<li>
|
||||
Real-time tracking for vehicles and assets, ensuring optimal operational efficiency.
|
||||
</li>
|
||||
<li>
|
||||
Order creation and management, facilitating seamless transaction processing.
|
||||
Service rates management, helping maintain financial transparency and profitability.
|
||||
</li>
|
||||
<li>
|
||||
Fleet management, providing a holistic view and control of your fleet.
|
||||
</li>
|
||||
<li>
|
||||
Third-party vendor integrations, allowing you to consolidate your resources.
|
||||
</li>
|
||||
<li>
|
||||
API & Webhooks that not only offer increased interconnectivity but also serve to facilitate integrations with other services and applications, making FleetOps a truly versatile solution.
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<strong>Storefront</strong>: Storefront is an extension that delivers headless commerce functionality, ideal for businesses aspiring to develop on-demand stores or marketplaces. It aims to facilitate seamless transactions while focusing on providing an excellent user experience.
|
||||
</li>
|
||||
<li><strong>Dev Console</strong>: The Dev Console extension is a developer's toolbox, providing resources such as:
|
||||
<ul>
|
||||
<li>
|
||||
API keys management, ensuring secure interactions with the application programming interface.
|
||||
</li>
|
||||
<li>
|
||||
Webhooks management, enabling real-time data exchanges.
|
||||
</li>
|
||||
<li>
|
||||
Sockets management, facilitating bi-directional client-server communication.
|
||||
</li>
|
||||
<li>
|
||||
Logs management, crucial for system monitoring and troubleshooting.
|
||||
</li>
|
||||
<li>
|
||||
API events management, keeping a pulse on system communications.
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
# 🏁 Getting Started
|
||||
|
||||
Before you can get started with Fleetbase, you'll need to make sure you have the following prerequisites:
|
||||
|
||||
<ol>
|
||||
<li>
|
||||
A computer running either Linux, Mac OS, or Windows
|
||||
</li>
|
||||
<li>Docker installed</li>
|
||||
<li>Git installed</li>
|
||||
<li>If you want to try now, the <a href="https://console.fleetbase.io/" target="_fleetbase" alt="Fleetbase">cloud hosted version of Fleetbase available here.</a></li>
|
||||
</ol>
|
||||
|
||||
# 🚦 Use Cases
|
||||
|
||||
Fleetbase's comprehensive suite of features and the modular design make it highly versatile, catering to a broad array of applications across different industries. Here are a few use cases:
|
||||
<ul>
|
||||
<li><strong>Logistics and Supply Chain Management</strong>: Fleetbase could be employed by a logistics company to streamline its operations. Real-time tracking provided by FleetOps would help maintain visibility of fleet vehicles and assets at all times. This would ensure timely delivery, reduce operational inefficiencies, and enable proactive management of any logistical issues. Additionally, the order creation and management feature could be used to manage deliveries, pickups, and routing.</li>
|
||||
## 💾 Install
|
||||
Getting up and running with Fleetbase via Docker is the quickest and most straightforward way. If you’d like to use Fleetbase without docker read the [full install guide in the Fleetbase documentation](https://docs.fleetbase.io/getting-started/install).
|
||||
|
||||
<li><strong>On-demand Delivery Services</strong>: On-demand services like food delivery or courier companies could utilize Fleetbase to manage their fleet of delivery agents. The real-time tracking functionality would help to optimize routes and ensure prompt deliveries, while the order creation and management system would efficiently handle incoming orders.</li>
|
||||
Make sure you have both the latest versions of docker and docker-compose installed on your system.
|
||||
|
||||
```bash
|
||||
git clone git@github.com:fleetbase/fleetbase.git
|
||||
cd fleetbase
|
||||
docker-compose up -d
|
||||
docker exec -ti fleetbase-application-1 bash
|
||||
sh deploy.sh
|
||||
```
|
||||
|
||||
### Accessing Fleetbase
|
||||
Once successfully installed and running you can then access the Fleetbase console on port 4200 and the API will be accessible from port 8000.
|
||||
|
||||
<li><strong>E-Commerce Platforms</strong>: E-commerce businesses could leverage Fleetbase to manage their backend logistics. The Storefront extension would enable seamless online transactions, while FleetOps could manage all aspects of the delivery process, ensuring a smooth shopping experience for the customers.</li>
|
||||
Fleetbase Console: http://localhost:4200
|
||||
Fleetbase API: http://localhost:8000
|
||||
|
||||
### Additional Configurations
|
||||
|
||||
**CORS:** If you’re installing directly on a server you may need to add your IP address or domain to the `api/config/cors.php` file in the `allowed_hosts` array.
|
||||
|
||||
<li><strong>Ride-Hailing Services</strong>: Fleetbase could be a perfect fit for ride-hailing or car rental services. FleetOps would manage real-time tracking of vehicles, maintaining optimal vehicle utilization, while the API and Webhooks would facilitate integration with mobile apps to provide real-time updates to customers.</li>
|
||||
**Routing:** Fleetbase ships with its own OSRM server hosted at `[bundle.routing.fleetbase.io](https://bundle.routing.fleetbase.io)` but you’re able to use your own or any other OSRM compatible server. You can modify this in the `console/environments` directory by modifying the env file of the environment you’re deploying and setting the `OSRM_HOST` to the OSRM server for Fleetbase to use.
|
||||
|
||||
<li><strong>Third-party Logistics (3PL) Provider</strong>: A 3PL provider could use Fleetbase for comprehensive management of its services. From real-time tracking of cargo to managing service rates and integration with other vendors in the supply chain, Fleetbase could provide an all-in-one solution.</li>
|
||||
|
||||
<li><strong>Developer Resource Management</strong>: Developers building complex, resource-intensive applications could benefit from Fleetbase's Dev Console. API keys and webhook management could streamline the secure interaction between different software components. At the same time, sockets, logs, and API events management tools would assist in maintaining, troubleshooting, and improving the system.</li>
|
||||
**Services:** There are a few environment variables which need to be set for Fleetbase to function with full features. If you’re deploying with docker then it’s easiest to just create a `docker-compose.override.yml` and supply the environment variables in this file.
|
||||
|
||||
<li><strong>Public Transportation Systems</strong>: City transportation services could use Fleetbase to optimize their bus or subway operations. With FleetOps, they could have real-time tracking of their vehicles, ensuring that schedules are met promptly and delays are handled effectively. Moreover, service rates management could assist in setting and adjusting fares, while the API and Webhooks functionality could integrate with public apps to provide real-time updates to commuters about arrivals, delays, and route changes.</li>
|
||||
```yaml
|
||||
version: “3.8”
|
||||
services:
|
||||
application:
|
||||
environment:
|
||||
MAIL_MAILER: (ses, smtp, mailgun, postmark, sendgrid)
|
||||
OSRM_HOST: https://bundle.routing.fleetbase.io
|
||||
IPINFO_API_KEY:
|
||||
GOOGLE_MAPS_API_KEY:
|
||||
GOOGLE_MAPS_LOCALE: us
|
||||
TWILIO_SID:
|
||||
TWILIO_TOKEN:
|
||||
TWILIO_FROM:
|
||||
CONSOLE_HOST: http://localhost:4200
|
||||
```
|
||||
|
||||
<li><strong>Fleet Leasing Companies</strong>: Fleet leasing companies could employ Fleetbase to manage their vehicle assets and track their status in real time. From managing service rates to ensuring the best utilization of assets, FleetOps could provide a holistic solution. Moreover, the Storefront extension could be used to list available vehicles and manage online reservations seamlessly.</li>
|
||||
|
||||
<li><strong>Emergency Services</strong>: Emergency services like ambulance or firefighting departments could use Fleetbase to manage their operations. FleetOps would provide real-time tracking, ensuring that emergency vehicles are dispatched quickly and the fastest routes are chosen. In addition, the API and Webhooks functionality could allow integration with emergency call centers, ensuring a seamless flow of information and a swift response to emergencies.</li>
|
||||
</ul>
|
||||
|
||||
Remember, these are just a few examples. Given the modular and extensible nature of Fleetbase, it can be customized and scaled to fit many other use cases across different industries.
|
||||
|
||||
# 💾 Installation
|
||||
|
||||
Getting Fleetbase up and running on your system using Docker and Docker-compose is straightforward. Please follow the steps below:
|
||||
|
||||
### Prerequisites
|
||||
|
||||
<ul>
|
||||
<li>Ensure that you have Docker and Docker-compose installed on your system. If not, you can download and install them from their respective official websites:
|
||||
<ul>
|
||||
<li><a href="https://docs.docker.com/get-docker/" target="_docker">Docker</a></li>
|
||||
<li><a href="https://docs.docker.com/compose/install/" target="_docker_compose">Docker Compose</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
Clone the Fleetbase repository to your local machine:
|
||||
<pre>git clone git@github.com:fleetbase/fleetbase.git</pre>
|
||||
</li>
|
||||
<li>
|
||||
Navigate to the cloned repository:
|
||||
<pre>cd fleetbase</pre>
|
||||
</li>
|
||||
<li>
|
||||
Initialize and pull submodules:
|
||||
<pre>git submodule update --init --recursive</pre>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
### Build and Run Fleetbase
|
||||
|
||||
<ol>
|
||||
<li>
|
||||
<strong>Start the Docker daemon:</strong>
|
||||
Ensure the Docker daemon is running on your machine. You can either start Docker Desktop or either executed by running:
|
||||
<pre>service docker start</pre>
|
||||
</li>
|
||||
<li>
|
||||
<strong>Build the Docker containers:</strong>
|
||||
Use Docker Compose to build and run the necessary containers. In the root directory of the Fleetbase repository, run:
|
||||
<pre>docker-compose up -d</pre>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
### Additional Steps
|
||||
|
||||
<ol>
|
||||
<li>Fleetbase will try to find the current hostname or public IP address to whitelist in for CORS, but if this fails you will need to manually add your hostname or instance URL to <code>api/config/cors.php</code> in the <code>allowed_origins</code> array. This will whitelist the console for CORS access to your instance backend.
|
||||
</li>
|
||||
<li>🛣 Routing! By default Fleetbase currently will use it's own routing engine which is hosted at <a href="https://routing.fleetbase.io" target="_fleetbase_routing_machine">https://routing.fleetbase.io</a>, this routing engine only works for a few enabled countries which include USA, Canada, Belgium, Spain, Serbia, Taiwan, Malaysia, Singapore, Brunei, Mongolia, India, Ghana. We can enable more regions and countries upon request. There is a Roadmap item to allow users to easily change to any routing engine provider such as Mapbox, Google Maps, and other 3rd Party Routing services. Optionally, you can switch out Fleetase Routing engine with any OSRM compatible service such as OpenStreetMap by changing the console environment variable <code>OSRM_HOST</code> which can be found in <code>console/environments/*.env</code>.
|
||||
</li>
|
||||
<li>If you find any bugs or unexpected issues please <a href="https://github.com/fleetbase/fleetbase/issues/new/choose">post an issue</a> to the repo or join our <a href="https://discord.gg/V7RVWRQ2Wm" target="_discord" alt="Fleetbase Discord">Discord</a>.
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
### Troubleshoot
|
||||
|
||||
Have an issue with the installation, try a few of these workarounds.
|
||||
|
||||
<ul>
|
||||
<li><strong>Installer not working?</strong> <br>If you encounter issues with the web based installer use this workaround to get going.
|
||||
<ol>
|
||||
<li>Login to the application container.
|
||||
<pre class="bash">docker exec -ti fleetbase-application-1 bash</pre>
|
||||
</li>
|
||||
<li>Manually run the database setup and migrations.
|
||||
<pre class="bash">php artisan mysql:createdb
|
||||
php artisan migrate:refresh --seed</pre>
|
||||
</li>
|
||||
<li>After completing these steps you should be able to proceed with account creation.</li>
|
||||
</ol>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
### Access Fleetbase
|
||||
|
||||
Now that Fleetbase is up and running via Docker you can find the console and the API accessible:
|
||||
|
||||
<ul>
|
||||
<li>Fleetbase Console: <code>http://localhost:4200</code></li>
|
||||
<li>Fleetbase API: <code>http://localhost:8000</code></li>
|
||||
</ul>
|
||||
|
||||
# 🧩 Extensions
|
||||
|
||||
Fleetbase extensions provide a powerful way to enhance and customize the functionality of Fleetbase to suit your specific needs. They are standalone modules that seamlessly integrate with Fleetbase's frontend and backend, allowing you to extend its capabilities.
|
||||
|
||||
### What are Fleetbase Extensions?
|
||||
Fleetbase Extensions are built using both a backend PHP package and a frontend Ember Engine Addon. They are designed to blend seamlessly into the Fleetbase ecosystem, utilizing shared services, utilities, stylesheets, components, and template helpers.
|
||||
|
||||
### How do Extensions Work?
|
||||
<ul>
|
||||
<li><strong>Backend</strong>: The backend of an extension is developed as a PHP package. This package should utilize the composer package <code>fleetbase/core-api</code>, which provides core API functionalities, making it easier to integrate your extension with Fleetbase's backend.</li>
|
||||
|
||||
<li><strong>Engine</strong>: The frontend of an extension is built as an Ember Engine Addon. The Addon must require the packages <code>@fleetbase/ember-core</code> and <code>@fleetbase/ember-ui</code>. The <code>@fleetbase/ember-core</code> package provides core services and utilities that help to align your extension with Fleetbase's frontend. The <code>@fleetbase/ember-ui</code> package, on the other hand, supplies all the stylesheets, components, and template helpers needed to design a Fleetbase extension that seamlessly matches the look and feel of the Fleetbase UI.</li>
|
||||
</ul>
|
||||
|
||||
### Building a Fleetbase Extension
|
||||
To create a Fleetbase extension, follow these steps:
|
||||
|
||||
<ul>
|
||||
<li><strong>Backend PHP Package Creation</strong>: Begin by creating a backend PHP package. Make sure to use the composer package <code>fleetbase/core-api</code> to ensure smooth integration with Fleetbase's backend.</li>
|
||||
|
||||
<li><strong>Frontend Ember Engine Addon</strong>: Next, you need to create the frontend of the extension using Ember Engine. Be sure to include the <code>@fleetbase/ember-core</code> and <code>@fleetbase/ember-ui</code> packages. These packages provide necessary services, utilities, and design components for aligning your extension with Fleetbase's UI.</li>
|
||||
|
||||
<li><strong>Integrate Your Extension</strong>: Once you have the backend and frontend ready, you can integrate your extension into Fleetbase by installing it via the respective package managers. In the future you will be able to publish your extension to the Fleetbase extensions repository making it available to all instances of Fleetbase with the ability to even sell your extension.</li>
|
||||
</ul>
|
||||
|
||||
With Fleetbase's modular architecture, you can develop your extensions to solve unique problems, enhance existing functionality, or add entirely new capabilities to your Fleetbase instance. This extensibility allows Fleetbase to adapt and evolve with your growing business needs.
|
||||
You can learn more about full installation, and configuration in the [official documentation](https://docs.fleetbase.io/getting-started/install).
|
||||
|
||||
# 📱 Apps
|
||||
|
||||
@@ -278,42 +121,36 @@ Fleetbase offers a few open sourced apps which are built on Fleetbase which can
|
||||
<li><a href="https://github.com/fleetbase/navigator-app">Navigator App</a>: Fleetbase based driver app which can be used for drivers to manage and update order, additionally provides real time driver location which can be viewed in the Fleetbase Console.</li>
|
||||
</ul>
|
||||
|
||||
# 🛣 Roadmap
|
||||
## 🛣️ Roadmap
|
||||
1. **Extensions Registry and Marketplace** ~ Allows users to publish and sell installable extensions on Fleetbase instances.
|
||||
2. **Inventory and Warehouse Management** ~ Pallet will be Fleetbase’s first official extension for WMS & Inventory.
|
||||
3. **Customer Facing Views** ~ Extensions will be able to create public/customer facing views tracking and management from outside of the console UI.
|
||||
4. **Binary Builds** ~ Run Fleetbase from a single binary.
|
||||
5. **Fleetbase CLI** ~ Official CLI for publishing and managing extensions, as well as scaffolding extensions.
|
||||
6. **Fleetbase for Desktop** ~ Desktop builds for OSX and Windows.
|
||||
7. **Custom Maps and Routing Engines** ~ Feature to enable easy integrations with custom maps and routing engines like Google Maps or Mapbox etc…
|
||||
|
||||
<ol>
|
||||
<li>Open Source Extension Repository</li>
|
||||
<li>🌎 Internationalize</li>
|
||||
<li>Multiple and Custom Routing Engine's in FleetOps</li>
|
||||
<li>Identity & Access Management (IAM) Extension</li>
|
||||
<li>Inventory and Warehouse Management Extension</li>
|
||||
<li>Freight Forwarder Quote Parser/ System Extension</li>
|
||||
</ol>
|
||||
|
||||
# 🪲 Bugs and 💡 Feature Requests
|
||||
## 🪲 Bugs and 💡 Feature Requests
|
||||
|
||||
Have a bug or a feature request? Please check the <a href="https://github.com/fleetbase/fleetbase/issues">issue tracker</a> and search for existing and closed issues. If your problem or idea is not addressed yet, please <a href="https://github.com/fleetbase/fleetbase/issues/new">open a new issue</a>.
|
||||
|
||||
# 📚 Documentation
|
||||
|
||||
View and contribute to our <a href="https://github.com/fleetbase/guides">Fleetbase Guide's</a> and <a href="https://github.com/fleetbase/api-reference">API Reference</a>
|
||||
|
||||
# 👨💻 Contributing
|
||||
## 👨💻 Contributing
|
||||
|
||||
Please read through our <a href="https://github.com/fleetbase/fleetbase/blob/main/CONTRIBUTING.md">contributing guidelines</a>. Included are directions for opening issues, coding standards, and notes on development.
|
||||
|
||||
# 👥 Community
|
||||
## 👥 Community
|
||||
|
||||
Get updates on Fleetbase's development and chat with the project maintainers and community members by joining our <a href="https://discord.gg/V7RVWRQ2Wm">Discord</a>.
|
||||
|
||||
<ul>
|
||||
<li>Follow <a href="https://twitter.com/fleetbase_io">@fleetbase_io on Twitter</a>.</li>
|
||||
<li>Follow <a href="https://x.com/fleetbase_io">@fleetbase_io on X</a>.</li>
|
||||
<li>Read and subscribe to <a href="https://www.fleetbase.io/blog-2">The Official Fleetbase Blog</a>.</li>
|
||||
<li>Ask and explore <a href="https://github.com/orgs/fleetbase/discussions">our GitHub Discussions</a>.</li>
|
||||
</ul>
|
||||
<p dir="auto">See the <a href="https://github.com/fleetbase/fleetbase/releases">Releases</a> section of our GitHub project for changelogs for each release version of Fleetbase.</p>
|
||||
<p>Release announcement posts on <a href="https://www.fleetbase.io/blog-2" rel="nofollow">the official Fleetbase blog</a> contain summaries of the most noteworthy changes made in each release.</p>
|
||||
|
||||
# Creators
|
||||
## Creators
|
||||
|
||||
<p dir="auto"><strong>Ronald A. Richardson</strong>- Co-founder & CTO</p>
|
||||
<img src="https://user-images.githubusercontent.com/58805033/230263021-212f2553-1269-473d-be94-313cb3eecfa5.png" alt="Ron Image" width="75" height="75" style="max-width: 100%;">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "laravel/laravel",
|
||||
"name": "fleetbase/fleetbase",
|
||||
"type": "project",
|
||||
"description": "The Laravel Framework.",
|
||||
"description": "Modular logistics and supply chain operating system (LSOS)",
|
||||
"keywords": [
|
||||
"framework",
|
||||
"laravel"
|
||||
@@ -9,9 +9,9 @@
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"php": "^8.0",
|
||||
"fleetbase/core-api": "^1.4.10",
|
||||
"fleetbase/fleetops-api": "^0.4.16",
|
||||
"fleetbase/storefront-api": "^0.3.3",
|
||||
"fleetbase/core-api": "^1.4.26",
|
||||
"fleetbase/fleetops-api": "^0.5.1",
|
||||
"fleetbase/storefront-api": "^0.3.10",
|
||||
"guzzlehttp/guzzle": "^7.0.1",
|
||||
"laravel/framework": "^10.0",
|
||||
"laravel/octane": "^2.3",
|
||||
@@ -32,6 +32,12 @@
|
||||
"nunomaduro/collision": "^7.0",
|
||||
"phpunit/phpunit": "^10.0"
|
||||
},
|
||||
"repositories": [
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "https://github.com/fleetbase/laravel-model-caching"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"App\\": "app/",
|
||||
@@ -84,6 +90,7 @@
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"secure-http": false,
|
||||
"optimize-autoloader": true,
|
||||
"preferred-install": "dist",
|
||||
"sort-packages": true,
|
||||
@@ -91,6 +98,6 @@
|
||||
"php-http/discovery": true
|
||||
}
|
||||
},
|
||||
"minimum-stability": "stable",
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true
|
||||
}
|
||||
|
||||
3070
api/composer.lock
generated
3070
api/composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -52,7 +52,7 @@ return [
|
||||
|
|
||||
*/
|
||||
|
||||
'url' => env('APP_URL', 'http://localhost'),
|
||||
'url' => env('APP_URL', 'http://localhost:8000'),
|
||||
|
||||
'asset_url' => env('ASSET_URL', null),
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ return [
|
||||
|
||||
'allowed_headers' => ['*'],
|
||||
|
||||
'exposed_headers' => [],
|
||||
'exposed_headers' => ['x-compressed-json', 'access-console-sandbox', 'access-console-sandbox-key'],
|
||||
|
||||
'max_age' => 0,
|
||||
|
||||
|
||||
@@ -58,6 +58,7 @@ return [
|
||||
'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')),
|
||||
'visibility_handler' => \League\Flysystem\GoogleCloudStorage\UniformBucketLevelAccessVisibility::class,
|
||||
],
|
||||
],
|
||||
|
||||
|
||||
@@ -17,3 +17,9 @@ php artisan fleetbase:seed
|
||||
|
||||
# Restart queue
|
||||
php artisan queue:restart
|
||||
|
||||
# Sync scheduler
|
||||
php artisan schedule-monitor:sync
|
||||
|
||||
# Clear cache
|
||||
php artisan cache:clear
|
||||
|
||||
@@ -7,6 +7,31 @@
|
||||
<InputGroup @name="S3 URL" @value={{this.s3Url}} disabled={{this.isLoading}} />
|
||||
<InputGroup @name="S3 Endpoint" @value={{this.s3Endpoint}} disabled={{this.isLoading}} />
|
||||
{{/if}}
|
||||
{{#if (eq this.driver "gcs")}}
|
||||
{{#if this.isGoogleCloudStorageEnvConfigued}}
|
||||
<div class="border border-yellow-900 shadow-sm rounded-lg bg-yellow-200 mb-4">
|
||||
<div class="px-3 py-2 text-sm text-yellow-900 flex items-center">
|
||||
<FaIcon @icon="triangle-exclamation" @size="md" class="mr-1.5" />
|
||||
Warning! GCS is already configured in the server environment. Changing values below may break this.
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
<InputGroup @name="GCS Bucket" @value={{this.gcsBucket}} disabled={{this.isLoading}} />
|
||||
<InputGroup @name="GCS Service Account Key File" @wrapperClass="">
|
||||
<div class="flex flex-row items-center mb-0i">
|
||||
<UploadButton @name="firebase-service-account" @accept="text/plain,text/javascript,application/json" @onFileAdded={{this.uploadGcsCredentialsFile}} @buttonText="Upload Service Account JSON" @icon="upload" class="w-auto m-0i mt-0i" />
|
||||
{{#if this.gcsCredentialsFile}}
|
||||
<div class="ml-2.5 text-sm dark:text-white text-black flex flex-row items-center border border-blue-500 rounded-lg px-2 py-0.5 -mt-1">
|
||||
<FaIcon @icon="file-text" @size="sm" class="mr-2 dark:text-white text-black" />
|
||||
<span>{{this.gcsCredentialsFile.original_filename}}</span>
|
||||
<a href="javascript:;" class="text-red-500 ml-2" {{on "click" this.removeGcsCredentialsFile}}>
|
||||
<FaIcon @icon="times" class="text-red-500" />
|
||||
</a>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</InputGroup>
|
||||
{{/if}}
|
||||
{{#if this.testResponse}}
|
||||
<div class="animate-pulse flex flex-row items-center rounded-lg border {{if (eq this.testResponse.status 'error') 'border-red-900 bg-red-800 text-red-100' 'border-green-900 bg-green-800 text-green-100'}} shadow-sm my-2 px-4 py-2">
|
||||
<FaIcon @icon={{if (eq this.testResponse.status 'error') 'triangle-exclamation' 'circle-check'}} class="mr-1.5 {{if (eq this.testResponse.status 'error') 'text-red-200' 'text-green-200'}}" />
|
||||
|
||||
@@ -6,6 +6,7 @@ import { action } from '@ember/object';
|
||||
export default class ConfigureFilesystemComponent extends Component {
|
||||
@service fetch;
|
||||
@service notifications;
|
||||
@service currentUser;
|
||||
@tracked isLoading = false;
|
||||
@tracked testResponse;
|
||||
@tracked disks = [];
|
||||
@@ -13,6 +14,10 @@ export default class ConfigureFilesystemComponent extends Component {
|
||||
@tracked s3Bucket = null;
|
||||
@tracked s3Url = null;
|
||||
@tracked s3Endpoint = null;
|
||||
@tracked gcsBucket = null;
|
||||
@tracked gcsCredentialsFileId = null;
|
||||
@tracked gcsCredentialsFile = null;
|
||||
@tracked isGoogleCloudStorageEnvConfigued = false;
|
||||
|
||||
/**
|
||||
* Creates an instance of ConfigureFilesystemComponent.
|
||||
@@ -59,6 +64,8 @@ export default class ConfigureFilesystemComponent extends Component {
|
||||
url: this.s3Url,
|
||||
endpoint: this.s3Endpoint,
|
||||
},
|
||||
gcsCredentialsFileId: this.gcsCredentialsFileId,
|
||||
gcsBucket: this.gcsBucket,
|
||||
})
|
||||
.then(() => {
|
||||
this.notifications.success('Filesystem configuration saved.');
|
||||
@@ -82,4 +89,31 @@ export default class ConfigureFilesystemComponent extends Component {
|
||||
this.isLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
@action removeGcsCredentialsFile() {
|
||||
this.gcsCredentialsFileId = undefined;
|
||||
this.gcsCredentialsFile = undefined;
|
||||
}
|
||||
|
||||
@action uploadGcsCredentialsFile(file) {
|
||||
try {
|
||||
this.fetch.uploadFile.perform(
|
||||
file,
|
||||
{
|
||||
path: 'gcs',
|
||||
subject_uuid: this.currentUser.companyId,
|
||||
subject_type: 'company',
|
||||
type: 'gcs_credentials',
|
||||
},
|
||||
(uploadedFile) => {
|
||||
console.log('uploadedFile', uploadedFile);
|
||||
this.gcsCredentialsFileId = uploadedFile.id;
|
||||
this.gcsCredentialsFile = uploadedFile;
|
||||
console.log('this.gcsCredentialsFile', this.gcsCredentialsFile);
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
this.notifications.serverError(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ export default class ConfigureNotificationChannelsComponent extends Component {
|
||||
production: true,
|
||||
};
|
||||
@tracked firebase = {
|
||||
credentials: ''
|
||||
credentials: '',
|
||||
};
|
||||
|
||||
constructor() {
|
||||
|
||||
1
console/app/components/console-wormhole.hbs
Normal file
1
console/app/components/console-wormhole.hbs
Normal file
@@ -0,0 +1 @@
|
||||
<div id="console-wormhole" />
|
||||
@@ -1,11 +1,15 @@
|
||||
import Component from '@glimmer/component';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { action, computed } from '@ember/object';
|
||||
import { computed } from '@ember/object';
|
||||
import { isArray } from '@ember/array';
|
||||
import { isBlank } from '@ember/utils';
|
||||
import { task } from 'ember-concurrency';
|
||||
import { storageFor } from 'ember-local-storage';
|
||||
import { add, isPast } from 'date-fns';
|
||||
import fetch from 'fetch';
|
||||
|
||||
export default class GithubCardComponent extends Component {
|
||||
@storageFor('local-cache') localCache;
|
||||
@tracked data;
|
||||
@tracked tags;
|
||||
@tracked isLoading = false;
|
||||
@@ -30,29 +34,47 @@ export default class GithubCardComponent extends Component {
|
||||
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.loadRepositoryData();
|
||||
this.loadRepositoryTags();
|
||||
this.getRepositoryData.perform();
|
||||
this.getRepositoryTags.perform();
|
||||
}
|
||||
|
||||
@action loadRepositoryData() {
|
||||
this.isLoading = true;
|
||||
@task *getRepositoryData() {
|
||||
// Check if cached data and expiration are available
|
||||
const cachedData = this.localCache.get('fleetbase-github-data');
|
||||
const expiration = this.localCache.get('fleetbase-github-data-expiration');
|
||||
|
||||
return fetch('https://api.github.com/repos/fleetbase/fleetbase')
|
||||
.then((response) => {
|
||||
return response.json().then((data) => {
|
||||
this.data = data;
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
this.isLoading = false;
|
||||
});
|
||||
// Check if the cached data is still valid
|
||||
if (cachedData && expiration && !isPast(new Date(expiration))) {
|
||||
// Use cached data
|
||||
this.data = cachedData;
|
||||
} else {
|
||||
// Fetch new data
|
||||
const response = yield fetch('https://api.github.com/repos/fleetbase/fleetbase');
|
||||
if (response.ok) {
|
||||
this.data = yield response.json();
|
||||
this.localCache.set('fleetbase-github-data', this.data);
|
||||
this.localCache.set('fleetbase-github-data-expiration', add(new Date(), { hours: 6 }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@action loadRepositoryTags() {
|
||||
return fetch('https://api.github.com/repos/fleetbase/fleetbase/tags').then((response) => {
|
||||
return response.json().then((data) => {
|
||||
this.tags = data;
|
||||
});
|
||||
});
|
||||
@task *getRepositoryTags() {
|
||||
// Check if cached tags and expiration are available
|
||||
const cachedTags = this.localCache.get('fleetbase-github-tags');
|
||||
const expiration = this.localCache.get('fleetbase-github-tags-expiration');
|
||||
|
||||
// Check if the cached tags are still valid
|
||||
if (cachedTags && expiration && !isPast(new Date(expiration))) {
|
||||
// Use cached tags
|
||||
this.tags = cachedTags;
|
||||
} else {
|
||||
// Fetch new tags
|
||||
const response = yield fetch('https://api.github.com/repos/fleetbase/fleetbase/tags');
|
||||
if (response.ok) {
|
||||
this.tags = yield response.json();
|
||||
this.localCache.set('fleetbase-github-tags', this.tags);
|
||||
this.localCache.set('fleetbase-github-tags-expiration', add(new Date(), { hours: 6 }));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
<div class="next-user-button" ...attributes>
|
||||
<BasicDropdown @defaultClass={{@wrapperClass}} @onOpen={{@onOpen}} @onClose={{@onClose}} @verticalPosition={{@verticalPosition}} @horizontalPosition={{@horizontalPosition}} @renderInPlace={{or @renderInPlace true}} @initiallyOpened={{@initiallyOpened}} as |dd|>
|
||||
<dd.Trigger class={{@triggerClass}}>
|
||||
<div class="next-org-button-trigger flex-shrink-0 {{if dd.isOpen 'is-open'}}">
|
||||
<FaIcon @icon="globe" @size="sm" />
|
||||
</div>
|
||||
</dd.Trigger>
|
||||
<dd.Content class={{@contentClass}}>
|
||||
<div class="next-dd-menu {{@dropdownMenuClass}} {{if dd.isOpen 'is-open'}}">
|
||||
{{#each-in this.availableLocales as |key country|}}
|
||||
<div class="px-1">
|
||||
<a href="javascript:;" class="next-dd-item" {{on "click" (fn this.changeLocale key)}}>
|
||||
<div class="flex flex-row items-center justify-between w-full">
|
||||
<div class="flex-1">
|
||||
<span class="mr-1">{{country.emoji}}</span>
|
||||
<span>{{country.language}}</span>
|
||||
</div>
|
||||
{{#if (eq this.currentLocale key)}}
|
||||
<div>
|
||||
<FaIcon @icon="check" class="text-green-400" />
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
{{/each-in}}
|
||||
</div>
|
||||
</dd.Content>
|
||||
</BasicDropdown>
|
||||
</div>
|
||||
@@ -1,144 +0,0 @@
|
||||
import Component from '@glimmer/component';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { action } from '@ember/object';
|
||||
import { task } from 'ember-concurrency-decorators';
|
||||
|
||||
export default class LocaleSelectorComponent extends Component {
|
||||
/**
|
||||
* Inject the intl service.
|
||||
*
|
||||
* @memberof LocaleSelectorComponent
|
||||
*/
|
||||
@service intl;
|
||||
|
||||
/**
|
||||
* Inject the intl service.
|
||||
*
|
||||
* @memberof LocaleSelectorComponent
|
||||
*/
|
||||
@service fetch;
|
||||
|
||||
/**
|
||||
* Tracks all the available locales.
|
||||
*
|
||||
* @memberof LocaleSelectorComponent
|
||||
*/
|
||||
@tracked locales = [];
|
||||
|
||||
/**
|
||||
* All available countries data.
|
||||
*
|
||||
* @memberof LocaleSelectorComponent
|
||||
*/
|
||||
@tracked countries = [];
|
||||
|
||||
/**
|
||||
* The current locale in use.
|
||||
*
|
||||
* @memberof LocaleSelectorComponent
|
||||
*/
|
||||
@tracked currentLocale;
|
||||
|
||||
/**
|
||||
* Creates an instance of LocaleSelectorComponent.
|
||||
* @memberof LocaleSelectorComponent
|
||||
*/
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
|
||||
this.locales = this.intl.locales;
|
||||
this.currentLocale = this.intl.primaryLocale;
|
||||
this.loadAvailableCountries.perform();
|
||||
|
||||
// Check for locale change
|
||||
this.intl.onLocaleChanged(() => {
|
||||
this.currentLocale = this.intl.primaryLocale;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the change of locale.
|
||||
* @param {string} selectedLocale - The selected locale.
|
||||
* @returns {void}
|
||||
* @memberof LocaleSelectorComponent
|
||||
* @method changeLocale
|
||||
* @instance
|
||||
* @action
|
||||
*/
|
||||
@action changeLocale(selectedLocale) {
|
||||
this.currentLocale = selectedLocale;
|
||||
this.intl.setLocale(selectedLocale);
|
||||
// Persist to server
|
||||
this.saveUserLocale.perform(selectedLocale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads available countries asynchronously.
|
||||
* @returns {void}
|
||||
* @memberof LocaleSelectorComponent
|
||||
* @method loadAvailableCountries
|
||||
* @instance
|
||||
* @task
|
||||
* @generator
|
||||
*/
|
||||
@task *loadAvailableCountries() {
|
||||
this.countries = yield this.fetch.get('lookup/countries', { columns: ['name', 'cca2', 'flag', 'emoji', 'languages'] });
|
||||
this.availableLocales = this._createAvailableLocaleMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the user's selected locale to the server.
|
||||
* @param {string} locale - The user's selected locale.
|
||||
* @returns {void}
|
||||
* @memberof LocaleSelectorComponent
|
||||
* @method saveUserLocale
|
||||
* @instance
|
||||
* @task
|
||||
* @generator
|
||||
*/
|
||||
@task *saveUserLocale(locale) {
|
||||
yield this.fetch.post('users/locale', { locale });
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a map of available locales.
|
||||
* @private
|
||||
* @returns {Object} - The map of available locales.
|
||||
* @memberof LocaleSelectorComponent
|
||||
* @method _createAvailableLocaleMap
|
||||
* @instance
|
||||
*/
|
||||
_createAvailableLocaleMap() {
|
||||
const localeMap = {};
|
||||
|
||||
for (let i = 0; i < this.locales.length; i++) {
|
||||
const locale = this.locales.objectAt(i);
|
||||
|
||||
localeMap[locale] = this._findCountryDataForLocale(locale);
|
||||
}
|
||||
|
||||
return localeMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds country data for a given locale.
|
||||
* @private
|
||||
* @param {string} locale - The locale to find country data for.
|
||||
* @returns {Object|null} - The country data or null if not found.
|
||||
* @memberof LocaleSelectorComponent
|
||||
* @method _findCountryDataForLocale
|
||||
* @instance
|
||||
*/
|
||||
_findCountryDataForLocale(locale) {
|
||||
const localeCountry = locale.split('-')[1];
|
||||
const country = this.countries.find((country) => country.cca2.toLowerCase() === localeCountry);
|
||||
|
||||
if (country) {
|
||||
// get the language
|
||||
country.language = Object.values(country.languages)[0];
|
||||
}
|
||||
|
||||
return country;
|
||||
}
|
||||
}
|
||||
13
console/app/components/modals/validate-password.hbs
Normal file
13
console/app/components/modals/validate-password.hbs
Normal file
@@ -0,0 +1,13 @@
|
||||
<Modal::Default @modalIsOpened={{@modalIsOpened}} @options={{@options}} @confirm={{@onConfirm}} @decline={{@onDecline}}>
|
||||
<div class="px-5">
|
||||
{{#if @options.body}}
|
||||
<p class="dark:text-gray-400 text-gray-700 mb-4">{{@options.body}}</p>
|
||||
{{/if}}
|
||||
<InputGroup>
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||
<InputGroup @name="Password" @type="password" @value={{this.password}} @wrapperClass="mb-0i" />
|
||||
<InputGroup @name="Confirm Password" @type="password" @value={{this.confirmPassword}} @wrapperClass="mb-0i" />
|
||||
</div>
|
||||
</InputGroup>
|
||||
</div>
|
||||
</Modal::Default>
|
||||
49
console/app/components/modals/validate-password.js
Normal file
49
console/app/components/modals/validate-password.js
Normal file
@@ -0,0 +1,49 @@
|
||||
import Component from '@glimmer/component';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { task } from 'ember-concurrency';
|
||||
|
||||
export default class ModalsValidatePasswordComponent extends Component {
|
||||
@service fetch;
|
||||
@service notifications;
|
||||
@tracked options = {};
|
||||
@tracked password;
|
||||
@tracked confirmPassword;
|
||||
|
||||
constructor(owner, { options }) {
|
||||
super(...arguments);
|
||||
this.options = options;
|
||||
this.setupOptions();
|
||||
}
|
||||
|
||||
setupOptions() {
|
||||
this.options.title = 'Validate Current Password';
|
||||
this.options.acceptButtonText = 'Validate Password';
|
||||
this.options.declineButtonHidden = true;
|
||||
this.options.confirm = (modal) => {
|
||||
modal.startLoading();
|
||||
return this.validatePassword.perform();
|
||||
};
|
||||
}
|
||||
|
||||
@task *validatePassword() {
|
||||
let isPasswordValid = false;
|
||||
|
||||
try {
|
||||
yield this.fetch.post('users/validate-password', {
|
||||
password: this.password,
|
||||
password_confirmation: this.confirmPassword,
|
||||
});
|
||||
|
||||
isPasswordValid = true;
|
||||
} catch (error) {
|
||||
this.notifications.serverError(error, 'Invalid current password.');
|
||||
}
|
||||
|
||||
if (typeof this.options.onValidated === 'function') {
|
||||
this.options.onValidated(isPasswordValid);
|
||||
}
|
||||
|
||||
return isPasswordValid;
|
||||
}
|
||||
}
|
||||
@@ -199,19 +199,13 @@ export default class AuthLoginController extends Controller {
|
||||
* @return {Promise<Transition>}
|
||||
* @memberof AuthLoginController
|
||||
*/
|
||||
sendUserForEmailVerification(email) {
|
||||
return this.fetch.post('auth/create-verification-session', { email, send: true }).then(({ token }) => {
|
||||
@action sendUserForEmailVerification(email) {
|
||||
return this.fetch.post('auth/create-verification-session', { email, send: true }).then(({ token, session }) => {
|
||||
return this.session.store.persist({ email }).then(() => {
|
||||
this.notifications.warning(this.intl.t('auth.login.unverified-notification'));
|
||||
return this.router
|
||||
.transitionTo('auth.verification', {
|
||||
queryParams: {
|
||||
token,
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
this.reset('error');
|
||||
});
|
||||
return this.router.transitionTo('auth.verification', { queryParams: { token, hello: session } }).then(() => {
|
||||
this.reset('error');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { action } from '@ember/object';
|
||||
import { task } from 'ember-concurrency';
|
||||
|
||||
export default class AuthResetPasswordController extends Controller {
|
||||
/**
|
||||
@@ -54,38 +54,23 @@ export default class AuthResetPasswordController extends Controller {
|
||||
@tracked password_confirmation;
|
||||
|
||||
/**
|
||||
* Loading stae of password reset.
|
||||
* The reset password task.
|
||||
*
|
||||
* @memberof AuthResetPasswordController
|
||||
*/
|
||||
@tracked isLoading;
|
||||
|
||||
/**
|
||||
* The reset password action.
|
||||
*
|
||||
* @memberof AuthResetPasswordController
|
||||
*/
|
||||
@action resetPassword(event) {
|
||||
// firefox patch
|
||||
@task *resetPassword(event) {
|
||||
event.preventDefault();
|
||||
|
||||
const { code, password, password_confirmation } = this;
|
||||
const { id } = this.model;
|
||||
|
||||
this.isLoading = true;
|
||||
try {
|
||||
yield this.fetch.post('auth/reset-password', { link: id, code, password, password_confirmation });
|
||||
} catch (error) {
|
||||
return this.notifications.serverError(error);
|
||||
}
|
||||
|
||||
this.fetch
|
||||
.post('auth/reset-password', { link: id, code, password, password_confirmation })
|
||||
.then(() => {
|
||||
this.notifications.success(this.intl.t('auth.reset-password.success-message'));
|
||||
|
||||
return this.router.transitionTo('auth.login');
|
||||
})
|
||||
.catch((error) => {
|
||||
this.notifications.serverError(error);
|
||||
})
|
||||
.finally(() => {
|
||||
this.isLoading = false;
|
||||
});
|
||||
this.notifications.success(this.intl.t('auth.reset-password.success-message'));
|
||||
yield this.router.transitionTo('auth.login');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,6 +150,21 @@ export default class AuthVerificationController extends Controller {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates input on the first render
|
||||
*
|
||||
* @param {HTMLElement} el
|
||||
* @memberof AuthVerificationController
|
||||
*/
|
||||
@action validateInitInput(el) {
|
||||
const value = el.value;
|
||||
if (value.length > 5) {
|
||||
this.isReadyToSubmit = true;
|
||||
} else {
|
||||
this.isReadyToSubmit = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Submits to verify code.
|
||||
*
|
||||
|
||||
@@ -174,6 +174,7 @@ export default class ConsoleController extends Controller {
|
||||
@action setSidebarContext(sidebarContext) {
|
||||
this.sidebarContext = sidebarContext;
|
||||
this.universe.sidebarContext = sidebarContext;
|
||||
this.universe.trigger('sidebarContext.available', sidebarContext);
|
||||
|
||||
if (this.hiddenSidebarRoutes.includes(this.router.currentRouteName)) {
|
||||
this.sidebarContext.hideNow();
|
||||
|
||||
@@ -34,17 +34,11 @@ export default class ConsoleAccountAuthController extends Controller {
|
||||
@service router;
|
||||
|
||||
/**
|
||||
* The user's current password.
|
||||
* @type {string}
|
||||
*/
|
||||
@tracked password;
|
||||
|
||||
/**
|
||||
* The user's confirmation of the new password.
|
||||
* Service for managing modals.
|
||||
*
|
||||
* @type {string}
|
||||
* @type {router}
|
||||
*/
|
||||
@tracked confirmPassword;
|
||||
@service modalsManager;
|
||||
|
||||
/**
|
||||
* The new password the user intends to set.
|
||||
@@ -60,13 +54,6 @@ export default class ConsoleAccountAuthController extends Controller {
|
||||
*/
|
||||
@tracked newConfirmPassword;
|
||||
|
||||
/**
|
||||
* Flag indicating whether the current password has been validated.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
@tracked isPasswordValidated = false;
|
||||
|
||||
/**
|
||||
* System-wide two-factor authentication configuration.
|
||||
*
|
||||
@@ -106,28 +93,6 @@ export default class ConsoleAccountAuthController extends Controller {
|
||||
this.loadUserTwoFaSettings.perform();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the user's current password.
|
||||
*
|
||||
* @method validatePassword
|
||||
* @param {Event} event - The event object triggering the action.
|
||||
*/
|
||||
@action validatePassword(event) {
|
||||
event.preventDefault();
|
||||
this.validatePasswordTask.perform();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates the task to change the user's password asynchronously.
|
||||
*
|
||||
* @method changeUserPasswordTask
|
||||
* @param {Event} event - The event object triggering the action.
|
||||
*/
|
||||
@action changeUserPassword(event) {
|
||||
event.preventDefault();
|
||||
this.changeUserPasswordTask.perform();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the event when two-factor authentication is toggled.
|
||||
*
|
||||
@@ -163,6 +128,58 @@ export default class ConsoleAccountAuthController extends Controller {
|
||||
this.saveUserTwoFaSettings.perform(this.twoFaSettings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates the task to change the user's password asynchronously.
|
||||
*
|
||||
* @method changePassword
|
||||
*/
|
||||
@task *changePassword(event) {
|
||||
// If from event fired
|
||||
if (event instanceof Event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
// Validate current password
|
||||
const isPasswordValid = yield this.validatePassword.perform();
|
||||
if (!isPasswordValid) {
|
||||
this.newPassword = undefined;
|
||||
this.newConfirmPassword = undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
yield this.fetch.post('users/change-password', {
|
||||
password: this.newPassword,
|
||||
password_confirmation: this.newConfirmPassword,
|
||||
});
|
||||
|
||||
this.notifications.success('Password change successfully.');
|
||||
} catch (error) {
|
||||
this.notifications.serverError(error, 'Failed to change password.');
|
||||
}
|
||||
|
||||
this.newPassword = undefined;
|
||||
this.newConfirmPassword = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Task to validate current password
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
@task *validatePassword() {
|
||||
let isPasswordValid = false;
|
||||
|
||||
yield this.modalsManager.show('modals/validate-password', {
|
||||
body: 'You must validate your current password before it can be changed.',
|
||||
onValidated: (isValid) => {
|
||||
isPasswordValid = isValid;
|
||||
},
|
||||
});
|
||||
|
||||
return isPasswordValid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates the task to save user-specific two-factor authentication settings asynchronously.
|
||||
*
|
||||
@@ -209,40 +226,4 @@ export default class ConsoleAccountAuthController extends Controller {
|
||||
}
|
||||
return twoFaConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates the task to validate the user's current password asynchronously.
|
||||
*
|
||||
* @method validatePasswordTask
|
||||
*/
|
||||
@task *validatePasswordTask() {
|
||||
try {
|
||||
yield this.fetch.post('users/validate-password', {
|
||||
password: this.password,
|
||||
password_confirmation: this.confirmPassword,
|
||||
});
|
||||
|
||||
this.isPasswordValidated = true;
|
||||
} catch (error) {
|
||||
this.notifications.serverError(error, 'Invalid current password.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates the task to change the user's password asynchronously.
|
||||
*
|
||||
* @method changeUserPasswordTask
|
||||
*/
|
||||
@task *changeUserPasswordTask() {
|
||||
try {
|
||||
yield this.fetch.post('users/change-password', {
|
||||
password: this.newPassword,
|
||||
password_confirmation: this.newConfirmPassword,
|
||||
});
|
||||
|
||||
this.notifications.success('Password change successfully.');
|
||||
} catch (error) {
|
||||
this.notifications.error('Failed to change password');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { action } from '@ember/object';
|
||||
import { alias } from '@ember/object/computed';
|
||||
import { task } from 'ember-concurrency';
|
||||
|
||||
export default class ConsoleAccountIndexController extends Controller {
|
||||
/**
|
||||
@@ -18,6 +18,7 @@ export default class ConsoleAccountIndexController extends Controller {
|
||||
* @memberof ConsoleAccountIndexController
|
||||
*/
|
||||
@service fetch;
|
||||
|
||||
/**
|
||||
* Inject the `notifications` service.
|
||||
*
|
||||
@@ -26,11 +27,11 @@ export default class ConsoleAccountIndexController extends Controller {
|
||||
@service notifications;
|
||||
|
||||
/**
|
||||
* The loading state of request.
|
||||
* Inject the `modalsManager` service.
|
||||
*
|
||||
* @memberof ConsoleAccountIndexController
|
||||
*/
|
||||
@tracked isLoading = false;
|
||||
@service modalsManager;
|
||||
|
||||
/**
|
||||
* Alias to the currentUser service user record.
|
||||
@@ -50,9 +51,9 @@ export default class ConsoleAccountIndexController extends Controller {
|
||||
file,
|
||||
{
|
||||
path: `uploads/${this.user.company_uuid}/users/${this.user.slug}`,
|
||||
key_uuid: this.user.id,
|
||||
key_type: `user`,
|
||||
type: `user_avatar`,
|
||||
subject_uuid: this.user.id,
|
||||
subject_type: 'user',
|
||||
type: 'user_avatar',
|
||||
},
|
||||
(uploadedFile) => {
|
||||
this.user.setProperties({
|
||||
@@ -66,27 +67,64 @@ export default class ConsoleAccountIndexController extends Controller {
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the Profile settings.
|
||||
* Starts the task to change password
|
||||
*
|
||||
* @return {Promise}
|
||||
* @param {Event} event
|
||||
* @memberof ConsoleAccountIndexController
|
||||
*/
|
||||
@action saveProfile() {
|
||||
const user = this.user;
|
||||
@task *saveProfile(event) {
|
||||
// If from event fired
|
||||
if (event instanceof Event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
this.isLoading = true;
|
||||
let canUpdateProfile = true;
|
||||
// If email has been changed prompt for password validation
|
||||
if (this.changedUserAttribute('email')) {
|
||||
canUpdateProfile = yield this.validatePassword.perform();
|
||||
}
|
||||
|
||||
return user
|
||||
.save()
|
||||
.then((user) => {
|
||||
if (canUpdateProfile === true) {
|
||||
try {
|
||||
const user = yield this.user.save();
|
||||
this.notifications.success('Profile changes saved.');
|
||||
this.currentUser.set('user', user);
|
||||
})
|
||||
.catch((error) => {
|
||||
} catch (error) {
|
||||
this.notifications.serverError(error);
|
||||
})
|
||||
.finally(() => {
|
||||
this.isLoading = false;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.user.rollbackAttributes();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Task to validate current password
|
||||
*
|
||||
* @return {boolean}
|
||||
* @memberof ConsoleAccountIndexController
|
||||
*/
|
||||
@task *validatePassword() {
|
||||
let isPasswordValid = false;
|
||||
|
||||
yield this.modalsManager.show('modals/validate-password', {
|
||||
body: 'You must validate your password to update the account email address.',
|
||||
onValidated: (isValid) => {
|
||||
isPasswordValid = isValid;
|
||||
},
|
||||
});
|
||||
|
||||
return isPasswordValid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if any user attribute has been changed
|
||||
*
|
||||
* @param {string} attributeKey
|
||||
* @return {boolean}
|
||||
* @memberof ConsoleAccountIndexController
|
||||
*/
|
||||
changedUserAttribute(attributeKey) {
|
||||
const changedAttributes = this.user.changedAttributes();
|
||||
return changedAttributes[attributeKey] !== undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,13 @@ export default class ConsoleAdminOrganizationsController extends Controller {
|
||||
*/
|
||||
@service filters;
|
||||
|
||||
/**
|
||||
* Inject the `crud` service
|
||||
*
|
||||
* @var {Service}
|
||||
*/
|
||||
@service crud;
|
||||
|
||||
/**
|
||||
* The search query param value.
|
||||
*
|
||||
@@ -166,4 +173,14 @@ export default class ConsoleAdminOrganizationsController extends Controller {
|
||||
@action goToCompany(company) {
|
||||
this.router.transitionTo('console.admin.organizations.index.users', company.public_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles dialog to export `drivers`
|
||||
*
|
||||
* @void
|
||||
*/
|
||||
@action exportOrganization() {
|
||||
const selections = this.table.selectedRows.map((_) => _.id);
|
||||
this.crud.export('companies', { params: { selections } });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,6 +60,13 @@ export default class ConsoleAdminOrganizationsIndexUsersController extends Contr
|
||||
*/
|
||||
@tracked company;
|
||||
|
||||
/**
|
||||
* The overlay context API.
|
||||
*
|
||||
* @memberof ConsoleAdminOrganizationsIndexUsersController
|
||||
*/
|
||||
@tracked contextApi;
|
||||
|
||||
/**
|
||||
* Queryable parameters for this controller's model
|
||||
*
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { action } from '@ember/object';
|
||||
import { task } from 'ember-concurrency';
|
||||
|
||||
export default class ConsoleAdminScheduleMonitorLogsController extends Controller {
|
||||
/**
|
||||
* The router service.
|
||||
*
|
||||
* @memberof ConsoleAdminScheduleMonitorLogsController
|
||||
*/
|
||||
@service router;
|
||||
/**
|
||||
* The fetch service.
|
||||
*
|
||||
* @memberof ConsoleAdminScheduleMonitorLogsController
|
||||
*/
|
||||
@service fetch;
|
||||
|
||||
/**
|
||||
* Tracked property for logs.
|
||||
* @type {Array}
|
||||
*/
|
||||
@tracked logs = [];
|
||||
|
||||
/**
|
||||
* Tracked property for the context API.
|
||||
* @type {Object}
|
||||
*/
|
||||
@tracked contextApi;
|
||||
|
||||
/**
|
||||
* Periodically reloads logs every 3 seconds.
|
||||
*
|
||||
* @memberof ConsoleAdminScheduleMonitorLogsController
|
||||
*/
|
||||
@task *reload(task) {
|
||||
this.logs = yield this.fetch.get(`schedule-monitor/${task.id}/logs`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the overlay component context object.
|
||||
*
|
||||
* @param {Object} contextApi
|
||||
* @memberof ConsoleAdminOrganizationsIndexUsersController
|
||||
*/
|
||||
@action setOverlayContext(contextApi) {
|
||||
this.contextApi = contextApi;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle closing the overlay.
|
||||
*
|
||||
* @return {Promise<Transition>}
|
||||
* @memberof ConsoleAdminOrganizationsIndexUsersController
|
||||
*/
|
||||
@action onPressClose() {
|
||||
if (this.contextApi && typeof this.contextApi.close === 'function') {
|
||||
this.contextApi.close();
|
||||
}
|
||||
|
||||
return this.router.transitionTo('console.admin.schedule-monitor');
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,12 @@
|
||||
export function initialize() {
|
||||
const socketClusterClientScript = document.createElement('script');
|
||||
socketClusterClientScript.src = '/assets/socketcluster-client.min.js';
|
||||
document.body.appendChild(socketClusterClientScript);
|
||||
// Check if the script already exists
|
||||
// Only insert the script tag if it doesn't already exist
|
||||
if (!document.querySelector('script[data-socketcluster-client]')) {
|
||||
const socketClusterClientScript = document.createElement('script');
|
||||
socketClusterClientScript.setAttribute('data-socketcluster-client', '1');
|
||||
socketClusterClientScript.src = '/assets/socketcluster-client.min.js';
|
||||
document.body.appendChild(socketClusterClientScript);
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
|
||||
@@ -29,6 +29,7 @@ export default class CategoryModel extends Model {
|
||||
@attr('string') slug;
|
||||
@attr('string') order;
|
||||
@attr('raw') translations;
|
||||
@attr('raw') meta;
|
||||
|
||||
/** @dates */
|
||||
@attr('date') deleted_at;
|
||||
|
||||
82
console/app/models/chat-attachment.js
Normal file
82
console/app/models/chat-attachment.js
Normal file
@@ -0,0 +1,82 @@
|
||||
import Model, { attr, belongsTo } from '@ember-data/model';
|
||||
import { getOwner } from '@ember/application';
|
||||
import { computed } from '@ember/object';
|
||||
import { format as formatDate, formatDistanceToNow, isValid as isValidDate } from 'date-fns';
|
||||
import isVideoFile from '@fleetbase/ember-core/utils/is-video-file';
|
||||
import isImageFile from '@fleetbase/ember-core/utils/is-image-file';
|
||||
import config from '@fleetbase/console/config/environment';
|
||||
|
||||
export default class ChatAttachment extends Model {
|
||||
/** @ids */
|
||||
@attr('string') chat_channel_uuid;
|
||||
@attr('string') sender_uuid;
|
||||
@attr('string') file_uuid;
|
||||
@attr('string') chat_message_uuid;
|
||||
|
||||
/** @relationships */
|
||||
@belongsTo('user', { async: true }) sender;
|
||||
@belongsTo('chat-channel', { async: true }) chatChannel;
|
||||
@belongsTo('file', { async: true }) file;
|
||||
|
||||
/** @attributes */
|
||||
@attr('string') url;
|
||||
@attr('string') filename;
|
||||
@attr('string') content_type;
|
||||
|
||||
/** @dates */
|
||||
@attr('date') created_at;
|
||||
@attr('date') updated_at;
|
||||
|
||||
/** @computed */
|
||||
@computed('updated_at') get updatedAgo() {
|
||||
if (!isValidDate(this.updated_at)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formatDistanceToNow(this.updated_at);
|
||||
}
|
||||
|
||||
@computed('updated_at') get updatedAt() {
|
||||
if (!isValidDate(this.updated_at)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formatDate(this.updated_at, 'PP HH:mm');
|
||||
}
|
||||
|
||||
@computed('created_at') get createdAgo() {
|
||||
if (!isValidDate(this.created_at)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formatDistanceToNow(this.created_at);
|
||||
}
|
||||
|
||||
@computed('created_at') get createdAt() {
|
||||
if (!isValidDate(this.created_at)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formatDate(this.created_at, 'PP HH:mm');
|
||||
}
|
||||
|
||||
@computed('content_type') get isVideo() {
|
||||
return isVideoFile(this.content_type);
|
||||
}
|
||||
|
||||
@computed('content_type') get isImage() {
|
||||
return isImageFile(this.content_type);
|
||||
}
|
||||
|
||||
/** @methods */
|
||||
downloadFromApi() {
|
||||
window.open(config.api.host + '/' + config.api.namespace + '/files/download?file=' + this.file_uuid, '_self');
|
||||
}
|
||||
|
||||
download() {
|
||||
const owner = getOwner(this);
|
||||
const fetch = owner.lookup('service:fetch');
|
||||
|
||||
return fetch.download('files/download', { file: this.file_uuid }, { fileName: this.filename, mimeType: this.content_type });
|
||||
}
|
||||
}
|
||||
87
console/app/models/chat-channel.js
Normal file
87
console/app/models/chat-channel.js
Normal file
@@ -0,0 +1,87 @@
|
||||
import Model, { attr, hasMany, belongsTo } from '@ember-data/model';
|
||||
import { computed } from '@ember/object';
|
||||
import { getOwner } from '@ember/application';
|
||||
import { format as formatDate, formatDistanceToNow, isValid as isValidDate } from 'date-fns';
|
||||
|
||||
export default class ChatChannelModel extends Model {
|
||||
/** @ids */
|
||||
@attr('string') public_id;
|
||||
@attr('string') company_uuid;
|
||||
@attr('string') created_by_uuid;
|
||||
|
||||
/** @attributes */
|
||||
@attr('string') name;
|
||||
@attr('string') title;
|
||||
@attr('number') unread_count;
|
||||
@attr('string') slug;
|
||||
@attr('array') feed;
|
||||
@attr('array') meta;
|
||||
|
||||
/** @relationships */
|
||||
@hasMany('chat-participant', { async: false }) participants;
|
||||
@belongsTo('chat-message', { async: false }) last_message;
|
||||
|
||||
/** @dates */
|
||||
@attr('date') created_at;
|
||||
@attr('date') updated_at;
|
||||
|
||||
/** @computed */
|
||||
@computed('updated_at') get updatedAgo() {
|
||||
if (!isValidDate(this.updated_at)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formatDistanceToNow(this.updated_at);
|
||||
}
|
||||
|
||||
@computed('updated_at') get updatedAt() {
|
||||
if (!isValidDate(this.updated_at)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formatDate(this.updated_at, 'PP HH:mm');
|
||||
}
|
||||
|
||||
@computed('created_at') get createdAgo() {
|
||||
if (!isValidDate(this.created_at)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formatDistanceToNow(this.created_at);
|
||||
}
|
||||
|
||||
@computed('created_at') get createdAt() {
|
||||
if (!isValidDate(this.created_at)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formatDate(this.created_at, 'PP HH:mm');
|
||||
}
|
||||
|
||||
/** @methods */
|
||||
toJSON() {
|
||||
return {
|
||||
company_uuid: this.company_uuid,
|
||||
name: this.name,
|
||||
meta: this.meta,
|
||||
};
|
||||
}
|
||||
|
||||
reloadParticipants() {
|
||||
const owner = getOwner(this);
|
||||
const store = owner.lookup('service:store');
|
||||
|
||||
return store.query('chat-participant', { chat_channel_uuid: this.id }).then((participants) => {
|
||||
this.set('participants', participants);
|
||||
return participants;
|
||||
});
|
||||
}
|
||||
|
||||
existsInFeed(type, record) {
|
||||
return this.feed.find((_) => _.type === type && _.record.id === record.id) !== undefined;
|
||||
}
|
||||
|
||||
doesntExistsInFeed(type, record) {
|
||||
return this.feed.find((_) => _.type === type && _.record.id === record.id) === undefined;
|
||||
}
|
||||
}
|
||||
54
console/app/models/chat-log.js
Normal file
54
console/app/models/chat-log.js
Normal file
@@ -0,0 +1,54 @@
|
||||
import Model, { attr } from '@ember-data/model';
|
||||
import { computed } from '@ember/object';
|
||||
import { format as formatDate, formatDistanceToNow, isValid as isValidDate } from 'date-fns';
|
||||
|
||||
export default class ChatLogModel extends Model {
|
||||
/** @ids */
|
||||
@attr('string') company_uuid;
|
||||
@attr('string') chat_channel_uuid;
|
||||
@attr('string') initiator_uuid;
|
||||
|
||||
/** @attributes */
|
||||
@attr('string') content;
|
||||
@attr('string') resolved_content;
|
||||
@attr('string') event_type;
|
||||
@attr('string') status;
|
||||
@attr('array') meta;
|
||||
|
||||
/** @dates */
|
||||
@attr('date') created_at;
|
||||
@attr('date') updated_at;
|
||||
|
||||
/** @computed */
|
||||
@computed('updated_at') get updatedAgo() {
|
||||
if (!isValidDate(this.updated_at)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formatDistanceToNow(this.updated_at, { addSuffix: true });
|
||||
}
|
||||
|
||||
@computed('updated_at') get updatedAt() {
|
||||
if (!isValidDate(this.updated_at)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formatDate(this.updated_at, 'PP HH:mm');
|
||||
}
|
||||
|
||||
@computed('created_at') get createdAgo() {
|
||||
if (!isValidDate(this.created_at)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formatDistanceToNow(this.created_at, { addSuffix: true });
|
||||
}
|
||||
|
||||
@computed('created_at') get createdAt() {
|
||||
if (!isValidDate(this.created_at)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formatDate(this.created_at, 'PP HH:mm');
|
||||
}
|
||||
}
|
||||
64
console/app/models/chat-message.js
Normal file
64
console/app/models/chat-message.js
Normal file
@@ -0,0 +1,64 @@
|
||||
import Model, { attr, belongsTo, hasMany } from '@ember-data/model';
|
||||
import { computed } from '@ember/object';
|
||||
import { format as formatDate, formatDistanceToNow, isValid as isValidDate } from 'date-fns';
|
||||
|
||||
export default class ChatMessage extends Model {
|
||||
/** @ids */
|
||||
@attr('string') chat_channel_uuid;
|
||||
@attr('string') sender_uuid;
|
||||
|
||||
/** @attributes */
|
||||
@attr('string') content;
|
||||
@attr('array') attachment_files;
|
||||
|
||||
/** @relationships */
|
||||
@belongsTo('chat-participant', { async: false }) sender;
|
||||
@hasMany('chat-attachment', { async: false }) attachments;
|
||||
@hasMany('chat-receipt', { async: false }) receipts;
|
||||
|
||||
/** @dates */
|
||||
@attr('date') created_at;
|
||||
@attr('date') updated_at;
|
||||
|
||||
/** @computed */
|
||||
@computed('updated_at') get updatedAgo() {
|
||||
if (!isValidDate(this.updated_at)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formatDistanceToNow(this.updated_at, { addSuffix: true });
|
||||
}
|
||||
|
||||
@computed('updated_at') get updatedAt() {
|
||||
if (!isValidDate(this.updated_at)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formatDate(this.updated_at, 'PP HH:mm');
|
||||
}
|
||||
|
||||
@computed('created_at') get createdAgo() {
|
||||
if (!isValidDate(this.created_at)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formatDistanceToNow(this.created_at, { addSuffix: true });
|
||||
}
|
||||
|
||||
@computed('created_at') get createdAt() {
|
||||
if (!isValidDate(this.created_at)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formatDate(this.created_at, 'PP HH:mm');
|
||||
}
|
||||
|
||||
/** @methods */
|
||||
hasReadReceipt(chatParticipant) {
|
||||
return chatParticipant && this.receipts.find((receipt) => chatParticipant.id === receipt.participant_uuid) !== undefined;
|
||||
}
|
||||
|
||||
doesntHaveReadReceipt(chatParticipant) {
|
||||
return !this.hasReadReceipt(chatParticipant);
|
||||
}
|
||||
}
|
||||
75
console/app/models/chat-participant.js
Normal file
75
console/app/models/chat-participant.js
Normal file
@@ -0,0 +1,75 @@
|
||||
import Model, { attr, belongsTo } from '@ember-data/model';
|
||||
import { computed } from '@ember/object';
|
||||
import { format as formatDate, formatDistanceToNow, isValid as isValidDate } from 'date-fns';
|
||||
|
||||
export default class ChatParticipant extends Model {
|
||||
/** @ids */
|
||||
@attr('string') user_uuid;
|
||||
@attr('string') chat_channel_uuid;
|
||||
|
||||
/** @attributes */
|
||||
@attr('string') name;
|
||||
@attr('string') username;
|
||||
@attr('string') phone;
|
||||
@attr('string') email;
|
||||
@attr('string') avatar_url;
|
||||
@attr('boolean') is_online;
|
||||
|
||||
/** @relationships */
|
||||
@belongsTo('user', { async: true }) user;
|
||||
@belongsTo('chat-channel', { async: true }) chatChannel;
|
||||
|
||||
/** @dates */
|
||||
@attr('date') last_seen_at;
|
||||
@attr('date') created_at;
|
||||
@attr('date') updated_at;
|
||||
|
||||
/** @computed */
|
||||
@computed('updated_at') get updatedAgo() {
|
||||
if (!isValidDate(this.updated_at)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formatDistanceToNow(this.updated_at);
|
||||
}
|
||||
|
||||
@computed('updated_at') get updatedAt() {
|
||||
if (!isValidDate(this.updated_at)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formatDate(this.updated_at, 'PP HH:mm');
|
||||
}
|
||||
|
||||
@computed('created_at') get createdAgo() {
|
||||
if (!isValidDate(this.created_at)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formatDistanceToNow(this.created_at);
|
||||
}
|
||||
|
||||
@computed('created_at') get createdAt() {
|
||||
if (!isValidDate(this.created_at)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formatDate(this.created_at, 'PP HH:mm');
|
||||
}
|
||||
|
||||
@computed('last_seen_at') get lastSeenAgo() {
|
||||
if (!isValidDate(this.last_seen_at)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formatDistanceToNow(this.last_seen_at);
|
||||
}
|
||||
|
||||
@computed('last_seen_at') get lastSeenAt() {
|
||||
if (!isValidDate(this.last_seen_at)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formatDate(this.last_seen_at, 'PP HH:mm');
|
||||
}
|
||||
}
|
||||
70
console/app/models/chat-receipt.js
Normal file
70
console/app/models/chat-receipt.js
Normal file
@@ -0,0 +1,70 @@
|
||||
import Model, { attr, belongsTo } from '@ember-data/model';
|
||||
import { computed } from '@ember/object';
|
||||
import { format as formatDate, formatDistanceToNow, isValid as isValidDate } from 'date-fns';
|
||||
|
||||
export default class ChatReceipt extends Model {
|
||||
/** @ids */
|
||||
@attr('string') participant_uuid;
|
||||
@attr('string') chat_message_uuid;
|
||||
|
||||
/** @relationships */
|
||||
@belongsTo('chat-participant', { async: true }) participant;
|
||||
@belongsTo('chat-message', { async: true }) chatMessage;
|
||||
|
||||
/** @attributes */
|
||||
@attr('string') participant_name;
|
||||
|
||||
/** @dates */
|
||||
@attr('date') created_at;
|
||||
@attr('date') updated_at;
|
||||
@attr('date') read_at;
|
||||
|
||||
/** @computed */
|
||||
@computed('updated_at') get updatedAgo() {
|
||||
if (!isValidDate(this.updated_at)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formatDistanceToNow(this.updated_at, { addSuffix: true });
|
||||
}
|
||||
|
||||
@computed('updated_at') get updatedAt() {
|
||||
if (!isValidDate(this.updated_at)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formatDate(this.updated_at, 'PP HH:mm');
|
||||
}
|
||||
|
||||
@computed('created_at') get createdAgo() {
|
||||
if (!isValidDate(this.created_at)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formatDistanceToNow(this.created_at, { addSuffix: true });
|
||||
}
|
||||
|
||||
@computed('created_at') get createdAt() {
|
||||
if (!isValidDate(this.created_at)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formatDate(this.created_at, 'PP HH:mm');
|
||||
}
|
||||
|
||||
@computed('read_at') get readAgo() {
|
||||
if (!isValidDate(this.read_at)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formatDistanceToNow(this.read_at, { addSuffix: true });
|
||||
}
|
||||
|
||||
@computed('read_at') get readAt() {
|
||||
if (!isValidDate(this.read_at)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formatDate(this.read_at, 'PP HH:mm');
|
||||
}
|
||||
}
|
||||
54
console/app/models/custom-field-value.js
Normal file
54
console/app/models/custom-field-value.js
Normal file
@@ -0,0 +1,54 @@
|
||||
import Model, { attr } from '@ember-data/model';
|
||||
import { computed } from '@ember/object';
|
||||
import { getOwner } from '@ember/application';
|
||||
import { format, formatDistanceToNow } from 'date-fns';
|
||||
|
||||
function isValidFileObjectJson(str) {
|
||||
return typeof str === 'string' && str.startsWith('{') && str.endsWith('}');
|
||||
}
|
||||
|
||||
export default class CustomFieldValueModel extends Model {
|
||||
/** @ids */
|
||||
@attr('string') company_uuid;
|
||||
@attr('string') custom_field_uuid;
|
||||
@attr('string') subject_uuid;
|
||||
@attr('string') subject_type;
|
||||
|
||||
/** @attributes */
|
||||
@attr('string') value;
|
||||
@attr('string') value_type;
|
||||
|
||||
/** @dates */
|
||||
@attr('date') created_at;
|
||||
@attr('date') updated_at;
|
||||
@attr('date') deleted_at;
|
||||
|
||||
/** @computed */
|
||||
@computed('value') get asFile() {
|
||||
const owner = getOwner(this);
|
||||
const fetch = owner.lookup(`service:fetch`);
|
||||
const value = this.value;
|
||||
if (!isValidFileObjectJson(value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const fileModel = fetch.jsonToModel(value, 'file');
|
||||
return fileModel;
|
||||
}
|
||||
|
||||
@computed('created_at') get createdAgo() {
|
||||
return formatDistanceToNow(this.created_at);
|
||||
}
|
||||
|
||||
@computed('created_at') get createdAt() {
|
||||
return format(this.created_at, 'PPP p');
|
||||
}
|
||||
|
||||
@computed('updated_at') get updatedAgo() {
|
||||
return formatDistanceToNow(this.updated_at);
|
||||
}
|
||||
|
||||
@computed('updated_at') get updatedAt() {
|
||||
return format(this.updated_at, 'PPP p');
|
||||
}
|
||||
}
|
||||
48
console/app/models/custom-field.js
Normal file
48
console/app/models/custom-field.js
Normal file
@@ -0,0 +1,48 @@
|
||||
import Model, { attr } from '@ember-data/model';
|
||||
import { computed } from '@ember/object';
|
||||
import { format, formatDistanceToNow } from 'date-fns';
|
||||
|
||||
export default class CustomFieldModel extends Model {
|
||||
/** @ids */
|
||||
@attr('string') company_uuid;
|
||||
@attr('string') category_uuid;
|
||||
@attr('string') subject_uuid;
|
||||
@attr('string') subject_type;
|
||||
|
||||
/** @attributes */
|
||||
@attr('string') name;
|
||||
@attr('string') description;
|
||||
@attr('string') help_text;
|
||||
@attr('string') label;
|
||||
@attr('string') type;
|
||||
@attr('string') component;
|
||||
@attr('string') default_value;
|
||||
@attr('number') order;
|
||||
@attr('boolean') required;
|
||||
@attr('boolean', { defaultValue: true }) editable;
|
||||
@attr('raw') options;
|
||||
@attr('raw') validation_rules;
|
||||
@attr('raw') meta;
|
||||
|
||||
/** @dates */
|
||||
@attr('date') created_at;
|
||||
@attr('date') updated_at;
|
||||
@attr('date') deleted_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('updated_at') get updatedAgo() {
|
||||
return formatDistanceToNow(this.updated_at);
|
||||
}
|
||||
|
||||
@computed('updated_at') get updatedAt() {
|
||||
return format(this.updated_at, 'PPP p');
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import { computed } from '@ember/object';
|
||||
import { not } from '@ember/object/computed';
|
||||
import { getOwner } from '@ember/application';
|
||||
import { format, formatDistanceToNow } from 'date-fns';
|
||||
import ENV from '@fleetbase/console/config/environment';
|
||||
import config from '@fleetbase/console/config/environment';
|
||||
import isVideoFile from '@fleetbase/ember-core/utils/is-video-file';
|
||||
import isImageFile from '@fleetbase/ember-core/utils/is-image-file';
|
||||
|
||||
@@ -64,13 +64,13 @@ export default class FileModel extends Model {
|
||||
|
||||
/** @methods */
|
||||
downloadFromApi() {
|
||||
window.open(ENV.api.host + '/' + ENV.api.namespace + '/files/download?file=' + this.id, '_self');
|
||||
window.open(config.api.host + '/' + config.api.namespace + '/files/download?file=' + this.id, '_self');
|
||||
}
|
||||
|
||||
download() {
|
||||
const owner = getOwner(this);
|
||||
const fetch = owner.lookup(`service:store`);
|
||||
const fetch = owner.lookup('service:fetch');
|
||||
|
||||
return fetch.download('files/download', { file: this.id });
|
||||
return fetch.download('files/download', { file: this.id }, { fileName: this.original_filename, mimeType: this.content_type });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import Model, { attr } from '@ember-data/model';
|
||||
import { computed } from '@ember/object';
|
||||
|
||||
import { format, formatDistanceToNow } from 'date-fns';
|
||||
|
||||
export default class NotificationModel extends Model {
|
||||
|
||||
@@ -26,11 +26,13 @@ export default class UserModel extends Model {
|
||||
@attr('string') type;
|
||||
@attr('string') session_status;
|
||||
@attr('string') status;
|
||||
@attr('boolean') is_online;
|
||||
@attr('boolean') is_admin;
|
||||
@attr('raw') types;
|
||||
@attr('raw') meta;
|
||||
|
||||
/** @dates */
|
||||
@attr('date') last_seen_at;
|
||||
@attr('date') phone_verified_at;
|
||||
@attr('date') email_verified_at;
|
||||
@attr('date') last_login;
|
||||
@@ -98,34 +100,52 @@ export default class UserModel extends Model {
|
||||
}
|
||||
|
||||
@computed('last_login') get lastLogin() {
|
||||
if (!this.last_login || !isValid(new Date(this.last_login))) {
|
||||
if (!this.last_login || !isValid(this.last_login)) {
|
||||
return 'Never';
|
||||
}
|
||||
|
||||
return format(new Date(this.last_login), 'PP');
|
||||
return format(this.last_login, 'PP p');
|
||||
}
|
||||
|
||||
@computed('updated_at') get updatedAgo() {
|
||||
if (!isValid(this.updated_at)) {
|
||||
return '-';
|
||||
}
|
||||
return formatDistanceToNow(this.updated_at);
|
||||
}
|
||||
|
||||
@computed('updated_at') get updatedAt() {
|
||||
if (!isValid(this.updated_at)) {
|
||||
return '-';
|
||||
}
|
||||
return format(this.updated_at, 'PPP p');
|
||||
}
|
||||
|
||||
@computed('updated_at') get updatedAtShort() {
|
||||
if (!isValid(this.updated_at)) {
|
||||
return '-';
|
||||
}
|
||||
return format(this.updated_at, 'PP');
|
||||
}
|
||||
|
||||
@computed('created_at') get createdAgo() {
|
||||
if (!isValid(this.created_at)) {
|
||||
return '-';
|
||||
}
|
||||
return formatDistanceToNow(this.created_at);
|
||||
}
|
||||
|
||||
@computed('created_at') get createdAt() {
|
||||
if (!isValid(this.created_at)) {
|
||||
return '-';
|
||||
}
|
||||
return format(this.created_at, 'PPP p');
|
||||
}
|
||||
|
||||
@computed('created_at') get createdAtShort() {
|
||||
if (!isValid(this.created_at)) {
|
||||
return '-';
|
||||
}
|
||||
return format(this.created_at, 'PP');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,13 +3,21 @@ import { inject as service } from '@ember/service';
|
||||
|
||||
export default class AuthResetPasswordRoute extends Route {
|
||||
@service store;
|
||||
@service fetch;
|
||||
@service router;
|
||||
@service notifications;
|
||||
@service intl;
|
||||
|
||||
async model(params) {
|
||||
return params;
|
||||
async model({ id }) {
|
||||
return this.fetch.get('auth/validate-verification', { id });
|
||||
}
|
||||
|
||||
async setupController(controller) {
|
||||
async setupController(controller, model) {
|
||||
super.setupController(...arguments);
|
||||
if (model.is_valid === false) {
|
||||
this.notifications.warning(this.intl.t('auth.reset-password.invalid-verification-code'));
|
||||
return this.router.transitionTo('auth');
|
||||
}
|
||||
|
||||
// set brand to controller
|
||||
controller.brand = await this.store.findRecord('brand', 1);
|
||||
|
||||
@@ -11,6 +11,12 @@ export default class AuthVerificationRoute extends Route {
|
||||
refreshModel: false,
|
||||
replace: true,
|
||||
},
|
||||
code: {
|
||||
refreshModel: false,
|
||||
},
|
||||
hello: {
|
||||
refreshModel: false,
|
||||
},
|
||||
};
|
||||
|
||||
beforeModel(transition) {
|
||||
|
||||
10
console/app/routes/console/admin/schedule-monitor.js
Normal file
10
console/app/routes/console/admin/schedule-monitor.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default class ConsoleAdminScheduleMonitorRoute extends Route {
|
||||
@service fetch;
|
||||
|
||||
model() {
|
||||
return this.fetch.get('schedule-monitor/tasks');
|
||||
}
|
||||
}
|
||||
14
console/app/routes/console/admin/schedule-monitor/logs.js
Normal file
14
console/app/routes/console/admin/schedule-monitor/logs.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default class ConsoleAdminScheduleMonitorLogsRoute extends Route {
|
||||
@service fetch;
|
||||
|
||||
model({ id }) {
|
||||
return this.fetch.get(`schedule-monitor/${id}`);
|
||||
}
|
||||
|
||||
async setupController(controller, model) {
|
||||
controller.logs = await this.fetch.get(`schedule-monitor/${model.id}/logs`);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,12 @@
|
||||
import Route from '@ember/routing/route';
|
||||
|
||||
export default class OnboardVerifyEmailRoute extends Route {}
|
||||
export default class OnboardVerifyEmailRoute extends Route {
|
||||
queryParams = {
|
||||
code: {
|
||||
refreshModel: false,
|
||||
},
|
||||
hello: {
|
||||
refreshModel: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
59
console/app/serializers/chat-channel.js
Normal file
59
console/app/serializers/chat-channel.js
Normal file
@@ -0,0 +1,59 @@
|
||||
import ApplicationSerializer from '@fleetbase/ember-core/serializers/application';
|
||||
import { EmbeddedRecordsMixin } from '@ember-data/serializer/rest';
|
||||
import { getOwner } from '@ember/application';
|
||||
import { isArray } from '@ember/array';
|
||||
|
||||
export default class ChatChannelSerializer extends ApplicationSerializer.extend(EmbeddedRecordsMixin) {
|
||||
/**
|
||||
* Embedded relationship attributes
|
||||
*
|
||||
* @var {Object}
|
||||
*/
|
||||
get attrs() {
|
||||
return {
|
||||
participants: { embedded: 'always' },
|
||||
last_message: { embedded: 'always' },
|
||||
};
|
||||
}
|
||||
|
||||
serialize(snapshot) {
|
||||
let json = {
|
||||
name: snapshot.attr('name'),
|
||||
meta: snapshot.attr('meta'),
|
||||
};
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
normalize(typeClass, hash) {
|
||||
if (isArray(hash.feed)) {
|
||||
hash.feed = this.serializeFeed(hash.feed);
|
||||
}
|
||||
|
||||
return super.normalize(...arguments);
|
||||
}
|
||||
|
||||
serializeFeed(feed = []) {
|
||||
return feed.map((item) => this.serializeItem(item)).sortBy('created_at');
|
||||
}
|
||||
|
||||
serializeItem(item) {
|
||||
switch (item.type) {
|
||||
case 'message':
|
||||
return { ...item, record: this.serializeItemType('chat-message', item.data) };
|
||||
case 'log':
|
||||
return { ...item, record: this.serializeItemType('chat-log', item.data) };
|
||||
case 'attachment':
|
||||
return { ...item, record: this.serializeItemType('chat-attachment', item.data) };
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
serializeItemType(modelType, data) {
|
||||
const owner = getOwner(this);
|
||||
const store = owner.lookup('service:store');
|
||||
const normalized = store.normalize(modelType, data);
|
||||
return store.push(normalized);
|
||||
}
|
||||
}
|
||||
17
console/app/serializers/chat-message.js
Normal file
17
console/app/serializers/chat-message.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import ApplicationSerializer from '@fleetbase/ember-core/serializers/application';
|
||||
import { EmbeddedRecordsMixin } from '@ember-data/serializer/rest';
|
||||
|
||||
export default class ChatMessageSerializer extends ApplicationSerializer.extend(EmbeddedRecordsMixin) {
|
||||
/**
|
||||
* Embedded relationship attributes
|
||||
*
|
||||
* @var {Object}
|
||||
*/
|
||||
get attrs() {
|
||||
return {
|
||||
sender: { embedded: 'always' },
|
||||
attachments: { embedded: 'always' },
|
||||
receipts: { embedded: 'always' },
|
||||
};
|
||||
}
|
||||
}
|
||||
3
console/app/serializers/chat-participant.js
Normal file
3
console/app/serializers/chat-participant.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import ApplicationSerializer from '@fleetbase/ember-core/serializers/application';
|
||||
|
||||
export default class ChatParticipantSerializer extends ApplicationSerializer {}
|
||||
@@ -6,14 +6,14 @@
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<form class="space-y-6" {{on "submit" this.resetPassword}}>
|
||||
<form class="space-y-6" {{on "submit" (perform this.resetPassword)}}>
|
||||
<InputGroup @name={{t "auth.reset-password.form.code.label"}} @value={{this.code}} @inputClass="form-input-lg" @helpText={{t "auth.reset-password.form.code.help-text"}} />
|
||||
<InputGroup @name={{t "auth.reset-password.form.password.label"}} @value={{this.password}} @type="password" @inputClass="form-input-lg" @helpText={{t "auth.reset-password.form.password.help-text"}} />
|
||||
<InputGroup @name={{t "auth.reset-password.form.confirm-password.label"}} @value={{this.password_confirmation}} @type="password" @inputClass="form-input-lg" @helpText={{t "auth.reset-password.form.confirm-password.help-text"}} />
|
||||
|
||||
<div class="flex space-x-2">
|
||||
<Button @icon="check" @size="lg" @type="primary" @buttonType="submit" @text={{t "auth.reset-password.form.submit-button"}} @onClick={{this.resetPassword}} @isLoading={{this.isLoading}} />
|
||||
<Button @size="lg" @buttonType="button" @text={{t "auth.reset-password.form.back-button"}} @onClick={{fn (transition-to "auth.login")}} @disabled={{this.isLoading}} />
|
||||
<Button @icon="check" @size="lg" @type="primary" @buttonType="submit" @text={{t "auth.reset-password.form.submit-button"}} @onClick={{perform this.resetPassword}} @isLoading={{not this.resetPassword.isIdle}} />
|
||||
<Button @size="lg" @buttonType="button" @text={{t "auth.reset-password.form.back-button"}} @onClick={{fn (transition-to "auth.login")}} @disabled={{not this.resetPassword.isIdle}} />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -17,10 +17,10 @@
|
||||
|
||||
<form class="mt-8" {{on "submit" this.verifyCode}}>
|
||||
<div class="flex items-center justify-between my-6">
|
||||
<OtpInput @onInputCompleted={{this.handleOtpInput}} />
|
||||
<OtpInput @onInputCompleted={{this.handleOtpInput}} @size={{6}} class="w-full" />
|
||||
</div>
|
||||
|
||||
<div id="otp-countdown-container" class="otp-countdown-container flex items-center justify-center min-h-12">
|
||||
<div id="otp-countdown-container" class="otp-countdown-container flex {{if this.isCodeExpired "flex-col" "flex-row"}} items-center justify-center min-h-12">
|
||||
{{#if this.countdownReady}}
|
||||
<Countdown @expiry={{this.twoFactorSessionExpiresAfter}} @countdownClass="text-lg" @onCountdownEnd={{this.handleCodeExpired}} />
|
||||
{{/if}}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
</div>
|
||||
|
||||
<form class="mt-8 space-y-6" {{on "submit" this.verifyCode}}>
|
||||
<InputGroup @type="tel" @name={{t "auth.verification.verification-input-label"}} @value={{this.code}} @helpText={{t "auth.verification.verification-code-text"}} @inputClass="input-lg" {{on "input" this.validateInput}} />
|
||||
<InputGroup @type="tel" @name={{t "auth.verification.verification-input-label"}} @value={{this.code}} @helpText={{t "auth.verification.verification-code-text"}} @inputClass="input-lg" {{on "input" this.validateInput}} {{did-insert this.validateInitInput}} />
|
||||
|
||||
<div class="flex flex-row items-center space-x-4">
|
||||
<Button @icon="check" @iconPrefix="fas" @buttonType="submit" @type="primary" @size="lg" @text="Verify & Continue" @isLoading={{this.isLoading}} @disabled={{this.isNotReadyToSubmit}} @onClick={{this.verifyCode}} />
|
||||
|
||||
@@ -14,9 +14,5 @@
|
||||
</Layout::Main>
|
||||
<Layout::MobileNavbar @brand={{@model}} @user={{this.user}} @organizations={{this.organizations}} @menuItems={{this.universe.headerMenuItems}} @extensions={{this.extensions}} @onAction={{this.onAction}} />
|
||||
</Layout::Container>
|
||||
|
||||
{{!-- Add Locale Selector to Header --}}
|
||||
<EmberWormhole @to="view-header-actions">
|
||||
<LocaleSelector class="mr-0.5" />
|
||||
</EmberWormhole>
|
||||
<div id="console-wormhole" />
|
||||
<ChatContainer />
|
||||
<ConsoleWormhole />
|
||||
|
||||
@@ -5,51 +5,28 @@
|
||||
<div class="container mx-auto h-screen" {{increase-height-by 500}}>
|
||||
<div class="max-w-3xl my-10 mx-auto space-y-6">
|
||||
<ContentPanel @title="Change Password" @open={{true}} @pad={{true}} @panelBodyClass="bg-white dark:bg-gray-800">
|
||||
{{#if this.isPasswordValidated}}
|
||||
<form id="change-password-form" aria-label="change-password" {{on "submit" this.changeUserPassword}}>
|
||||
<legend class="mb-3">Change Password</legend>
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||
<InputGroup @name="Enter new Password" @type="password" @value={{this.newPassword}} />
|
||||
<InputGroup @name="Confirm Password" @type="password" @value={{this.newConfirmPassword}} />
|
||||
</div>
|
||||
<Button @type="primary" @buttonType="submit" @text="Confirm & Change Password" @icon="save" {{on "click" this.changeUserPassword}} />
|
||||
</form>
|
||||
{{else}}
|
||||
<form id="validate-password-form" aria-label="validate-password" {{on "submit" this.validatePassword}}>
|
||||
<legend class="mb-3">Validate Current Password</legend>
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||
<InputGroup @name="Password" @type="password" @value={{this.password}} />
|
||||
<InputGroup @name="Confirm Password" @type="password" @value={{this.confirmPassword}} />
|
||||
</div>
|
||||
<Button @type="primary" @buttonType="submit" @text="Continue" @icon="check" {{on "click" this.validatePassword}} />
|
||||
</form>
|
||||
{{/if}}
|
||||
<form id="change-password-form" aria-label="change-password" {{on "submit" (perform this.changePassword)}}>
|
||||
<legend class="mb-3">Change Password</legend>
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||
<InputGroup @name="Enter new Password" @type="password" @value={{this.newPassword}} />
|
||||
<InputGroup @name="Confirm Password" @type="password" @value={{this.newConfirmPassword}} />
|
||||
</div>
|
||||
<Button @type="primary" @buttonType="submit" @text="Confirm & Change Password" @icon="save" {{on "click" (perform this.changePassword)}} />
|
||||
</form>
|
||||
</ContentPanel>
|
||||
|
||||
{{#if this.isSystemTwoFaEnabled}}
|
||||
<ContentPanel @title="2FA Settings" @open={{true}} @pad={{true}} @panelBodyClass="bg-white dark:bg-gray-800">
|
||||
<div class="mb-3">
|
||||
{{#if this.loadUserTwoFaSettings.isIdle}}
|
||||
<TwoFaSettings
|
||||
@twoFaMethods={{this.methods}}
|
||||
@twoFaSettings={{this.twoFaSettings}}
|
||||
@onTwoFaToggled={{this.onTwoFaToggled}}
|
||||
@onTwoFaMethodSelected={{this.onTwoFaMethodSelected}}
|
||||
/>
|
||||
<TwoFaSettings @twoFaMethods={{this.methods}} @twoFaSettings={{this.twoFaSettings}} @onTwoFaToggled={{this.onTwoFaToggled}} @onTwoFaMethodSelected={{this.onTwoFaMethodSelected}} />
|
||||
{{else}}
|
||||
<div class="flex items-center justify-center p-4">
|
||||
<Spinner @loadingMessage="Loading User 2FA Settings..." @wrapperClass="flex flex-row" @iconClass="mr-2" />
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
<Button
|
||||
@type="primary"
|
||||
@buttonType="submit"
|
||||
@text="Save 2FA Settings"
|
||||
@icon="save"
|
||||
@onClick={{this.saveTwoFactorAuthSettings}}
|
||||
@isLoading={{this.saveUserTwoFaSettings.isRunning}}
|
||||
/>
|
||||
<Button @type="primary" @buttonType="submit" @text="Save 2FA Settings" @icon="save" @onClick={{this.saveTwoFactorAuthSettings}} @isLoading={{this.saveUserTwoFaSettings.isRunning}} />
|
||||
</ContentPanel>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<Layout::Section::Header @title={{t "common.profile"}} />
|
||||
|
||||
<Layout::Section::Body class="overflow-y-scroll h-full">
|
||||
<div class="container mx-auto h-screen" {{increase-height-by 500}}>
|
||||
<div class="container mx-auto h-screen">
|
||||
<div class="max-w-3xl my-10 mx-auto">
|
||||
<ContentPanel @title={{t "common.your-profile"}} @open={{true}} @pad={{true}} @panelBodyClass="bg-white dark:bg-gray-800">
|
||||
<form class="flex flex-col md:flex-row" {{on "submit" this.saveProfile}}>
|
||||
<form class="flex flex-col md:flex-row" {{on "submit" (perform this.saveProfile)}}>
|
||||
<div class="w-32 flex flex-col justify-center mb-6 mr-6">
|
||||
<Image src={{this.user.avatar_url}} @fallbackSrc={{config "defaultValues.userImage"}} alt={{this.user.name}} class="w-32 h-32 rounded-md" />
|
||||
<FileUpload @name={{t "console.account.index.photos"}} @accept="image/*" @onFileAdded={{this.uploadNewPhoto}} @labelClass="flex flex-row items-center justify-center" as |queue|>
|
||||
@@ -35,11 +35,12 @@
|
||||
<InputGroup @name={{t "common.date-of-birth"}} @type="date" @value={{this.user.date_of_birth}} />
|
||||
</div>
|
||||
<div class="mt-3 flex items-center justify-end">
|
||||
<Button @buttonType="submit" @type="primary" @size="lg" @icon="save" @text={{t "common.save-button-text"}} @onClick={{this.saveProfile}} @isLoading={{this.isLoading}} />
|
||||
<Button @buttonType="submit" @type="primary" @size="lg" @icon="save" @text={{t "common.save-button-text"}} @onClick={{perform this.saveProfile}} @isLoading={{not this.saveProfile.isIdle}} />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</ContentPanel>
|
||||
</div>
|
||||
</div>
|
||||
<Spacer @height="500px" />
|
||||
</Layout::Section::Body>
|
||||
@@ -6,6 +6,7 @@
|
||||
<Layout::Sidebar::Item @route="console.admin.branding" @icon="palette">{{t "common.branding"}}</Layout::Sidebar::Item>
|
||||
<Layout::Sidebar::Item @route="console.admin.notifications" @icon="bell">{{t "common.notifications"}}</Layout::Sidebar::Item>
|
||||
<Layout::Sidebar::Item @route="console.admin.two-fa-settings" @icon="shield-halved">{{t "common.2fa-config"}}</Layout::Sidebar::Item>
|
||||
<Layout::Sidebar::Item @route="console.admin.schedule-monitor" @icon="calendar-check">{{t "console.admin.schedule-monitor.schedule-monitor"}}</Layout::Sidebar::Item>
|
||||
{{#each this.universe.adminMenuItems as |menuItem|}}
|
||||
<Layout::Sidebar::Item
|
||||
@onClick={{fn this.universe.transitionMenuItem "console.admin.virtual" menuItem}}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
</Layout::Section::Header>
|
||||
|
||||
<Layout::Section::Body class="overflow-y-scroll h-full">
|
||||
<div class="container mx-auto h-screen" {{increase-height-by 300}}>
|
||||
<div class="container mx-auto h-screen">
|
||||
<div class="max-w-3xl my-10 mx-auto space-y-6">
|
||||
<ContentPanel @title={{t "console.admin.branding.title"}} @open={{true}} @pad={{true}} @panelBodyClass="bg-white dark:bg-gray-800">
|
||||
<form class="flex flex-col" {{on "submit" this.save}}>
|
||||
@@ -63,4 +63,5 @@
|
||||
</ContentPanel>
|
||||
</div>
|
||||
</div>
|
||||
<Spacer @height="300px" />
|
||||
</Layout::Section::Body>
|
||||
@@ -4,7 +4,7 @@
|
||||
</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="container mx-auto h-screen">
|
||||
<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) (t "console.admin.notifications.notification-settings") }} @open={{true}} @pad={{true}} @panelBodyClass="bg-white dark:bg-gray-800">
|
||||
@@ -29,4 +29,5 @@
|
||||
{{/each-in}}
|
||||
</div>
|
||||
</div>
|
||||
<Spacer @height="300px" />
|
||||
</Layout::Section::Body>
|
||||
@@ -1,13 +1,15 @@
|
||||
{{page-title (t "console.admin.organizations.index.title")}}
|
||||
{{!-- template-lint-disable no-unbound --}}
|
||||
<Layout::Section::Header @title={{t "console.admin.organizations.index.title"}} @searchQuery={{unbound this.query}} @onSearch={{this.search}} />
|
||||
{{! template-lint-disable no-unbound }}
|
||||
<Layout::Section::Header @title={{t "console.admin.organizations.index.title"}} @searchQuery={{unbound this.query}} @onSearch={{this.search}}>
|
||||
<Button @icon="long-arrow-up" @iconClass="rotate-icon-45" @text={{t "common.export"}} @wrapperClass="hidden md:flex" @onClick={{this.exportOrganization}} />
|
||||
</Layout::Section::Header>
|
||||
|
||||
<Layout::Section::Body>
|
||||
<Table
|
||||
@rows={{@model}}
|
||||
@columns={{this.columns}}
|
||||
@selectable={{false}}
|
||||
@canSelectAll={{false}}
|
||||
@selectable={{true}}
|
||||
@canSelectAll={{true}}
|
||||
@onSetup={{fn (mut this.table)}}
|
||||
@pagination={{true}}
|
||||
@paginationMeta={{@model.meta}}
|
||||
|
||||
34
console/app/templates/console/admin/schedule-monitor.hbs
Normal file
34
console/app/templates/console/admin/schedule-monitor.hbs
Normal file
@@ -0,0 +1,34 @@
|
||||
{{page-title (t "console.admin.schedule-monitor.schedule-monitor")}}
|
||||
<Layout::Section::Header @title={{t "console.admin.schedule-monitor.schedule-monitor"}} />
|
||||
|
||||
<Layout::Section::Body class="overflow-y-scroll h-full">
|
||||
<div class="next-table-wrapper">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th {{set-width "300px"}}>{{t "console.admin.schedule-monitor.name"}}</th>
|
||||
<th {{set-width "100px"}}>{{t "console.admin.schedule-monitor.type"}}</th>
|
||||
<th {{set-width "90px"}}>{{t "console.admin.schedule-monitor.timezone"}}</th>
|
||||
<th>{{t "console.admin.schedule-monitor.last-started"}}</th>
|
||||
<th>{{t "console.admin.schedule-monitor.last-finished"}}</th>
|
||||
<th>{{t "console.admin.schedule-monitor.last-failure"}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#each @model as |task|}}
|
||||
<tr>
|
||||
<td>
|
||||
<LinkTo @route="console.admin.schedule-monitor.logs" @model={{task}}>{{task.name}}</LinkTo>
|
||||
</td>
|
||||
<td>{{task.type}}</td>
|
||||
<td>{{task.timezone}}</td>
|
||||
<td>{{n-a task.last_started_at_fmt}}</td>
|
||||
<td>{{n-a task.last_finished_at_fmt}}</td>
|
||||
<td>{{n-a task.last_failed_at_fmt}}</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</Layout::Section::Body>
|
||||
{{outlet}}
|
||||
@@ -0,0 +1,34 @@
|
||||
{{page-title (concat (t "console.admin.schedule-monitor.schedule-monitor") " - " @model.name)}}
|
||||
<Overlay @isOpen={{true}} @onLoad={{this.setOverlayContext}} @position="right" @noBackdrop={{true}} @fullHeight={{true}} @width="600px" @isResizable={{true}}>
|
||||
<Overlay::Header @title={{concat (t "console.admin.schedule-monitor.task-logs-for") @model.name}} @titleClass="max-w-400px truncate" @hideStatusDot={{true}} @titleWrapperClass="leading-5">
|
||||
<div class="flex flex-1 justify-end">
|
||||
<Button @type="default" @icon="times" @helpText={{t "common.close-and-save"}} @onClick={{this.onPressClose}} />
|
||||
</div>
|
||||
</Overlay::Header>
|
||||
|
||||
<Overlay::Body>
|
||||
<div class="p-4">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div class="text-sm">{{t "console.admin.schedule-monitor.showing-last-count" count=20}}</div>
|
||||
<Button @size="xs" @icon="arrows-rotate" @onClick={{perform this.reload @model}} @isLoading={{not this.reload.isIdle}} />
|
||||
</div>
|
||||
<div class="space-y-4">
|
||||
{{#each this.logs as |log|}}
|
||||
<div class="bg-gray-100 border border-gray-200 dark:border-gray-700 dark:bg-gray-800 rounded-lg p-2 font-mono text-xs">
|
||||
<div class="font-mono"><span class="font-semibold font-mono">{{t "console.admin.schedule-monitor.date"}}:</span> {{log.created_at_fmt}}</div>
|
||||
<div class="font-mono"><span class="font-semibold font-mono">{{t "console.admin.schedule-monitor.memory"}}:</span> {{format-bytes log.meta.memory}}</div>
|
||||
<div class="font-mono"><span class="font-semibold font-mono">{{t "console.admin.schedule-monitor.runtime"}}:</span> {{format-milliseconds log.meta.runtime}}</div>
|
||||
<div class="font-semibold font-mono mb-2">{{t "console.admin.schedule-monitor.output"}}:</div>
|
||||
<div class="whitespace-pre-line overflow-hidden bg-black text-green-400 rounded-lg p-2 font-mono text-xs border border-gray-900">
|
||||
{{#if log.meta.output}}
|
||||
{{log.meta.output}}
|
||||
{{else}}
|
||||
{{t "console.admin.schedule-monitor.no-output"}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
</Overlay::Body>
|
||||
</Overlay>
|
||||
@@ -4,7 +4,7 @@
|
||||
</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="container mx-auto h-screen">
|
||||
<div class="max-w-3xl my-10 mx-auto space-y-4">
|
||||
<ContentPanel @title="2FA Config" @open={{true}} @pad={{true}} @panelBodyClass="bg-white dark:bg-gray-800">
|
||||
{{#if this.loadSystemTwoFaConfig.isIdle}}
|
||||
@@ -25,4 +25,5 @@
|
||||
</ContentPanel>
|
||||
</div>
|
||||
</div>
|
||||
<Spacer @height="300px" />
|
||||
</Layout::Section::Body>
|
||||
@@ -18,7 +18,7 @@
|
||||
</div>
|
||||
|
||||
<form class="mt-8 space-y-6" {{on "submit" this.verifyCode}}>
|
||||
<InputGroup @type="tel" @name={{t "onboard.verify-email.verification-input-label"}} @value={{this.code}} @helpText={{t "onboard.verify-email.verification-code-text"}} @inputClass="input-lg" {{on "input" this.validateInput}} />
|
||||
<InputGroup @type="tel" @name={{t "onboard.verify-email.verification-input-label"}} @value={{this.code}} @helpText={{t "onboard.verify-email.verification-code-text"}} @inputClass="input-lg" {{on "input" this.validateInput}} {{did-insert this.validateInitInput}} />
|
||||
|
||||
<div class="flex flex-row items-center space-x-4">
|
||||
<Button @icon="check" @iconPrefix="fas" @buttonType="submit" @type="primary" @size="lg" @text="Verify & Continue" @isLoading={{this.isLoading}} @disabled={{this.isNotReadyToSubmit}} @onClick={{this.verifyCode}} />
|
||||
|
||||
@@ -46,9 +46,13 @@ 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'),
|
||||
entityImage: getenv('DEFAULT_ENTITY_IMAGE', 'https://flb-assets.s3-ap-southeast-1.amazonaws.com/static/parcels/medium.png'),
|
||||
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'),
|
||||
vehicleAvatar: getenv('DEFAULT_VEHICLE_AVATAR', 'https://flb-assets.s3-ap-southeast-1.amazonaws.com/static/vehicle-icons/mini_bus.svg'),
|
||||
driverAvatar: getenv('DEFAULT_DRIVER_AVATAR', 'https://flb-assets.s3-ap-southeast-1.amazonaws.com/static/driver-icons/moto-driver.png'),
|
||||
placeAvatar: getenv('DEFAULT_PLACE_AVATAR', 'https://flb-assets.s3-ap-southeast-1.amazonaws.com/static/place-icons/basic-building.png'),
|
||||
extensionIcon: getenv('DEFAULT_EXTENSION_ICON', 'https://flb-assets.s3.ap-southeast-1.amazonaws.com/static/default-extension-icon.svg'),
|
||||
},
|
||||
|
||||
'ember-simple-auth': {
|
||||
@@ -60,6 +64,11 @@ module.exports = function (environment) {
|
||||
keyDelimiter: '/',
|
||||
includeEmberDataSupport: true,
|
||||
},
|
||||
|
||||
'ember-cli-notifications': {
|
||||
autoClear: true,
|
||||
clearDuration: 1000 * 3.5,
|
||||
},
|
||||
};
|
||||
|
||||
if (environment === 'development') {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "@fleetbase/console",
|
||||
"version": "0.4.9",
|
||||
"version": "0.4.26",
|
||||
"private": true,
|
||||
"description": "Fleetbase Console",
|
||||
"description": "Modular logistics and supply chain operating system (LSOS)",
|
||||
"repository": "https://github.com/fleetbase/fleetbase",
|
||||
"license": "MIT",
|
||||
"author": "Fleetbase Pte Ltd <hello@fleetbase.io>",
|
||||
@@ -29,13 +29,13 @@
|
||||
"test:ember": "ember test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fleetbase/ember-core": "^0.2.4",
|
||||
"@fleetbase/ember-ui": "^0.2.11",
|
||||
"@fleetbase/fleetops-engine": "^0.4.16",
|
||||
"@fleetbase/fleetops-data": "^0.1.9",
|
||||
"@fleetbase/storefront-engine": "^0.3.3",
|
||||
"@fleetbase/dev-engine": "^0.2.1",
|
||||
"@fleetbase/iam-engine": "^0.0.9",
|
||||
"@fleetbase/ember-core": "^0.2.11",
|
||||
"@fleetbase/ember-ui": "^0.2.17",
|
||||
"@fleetbase/fleetops-engine": "^0.5.1",
|
||||
"@fleetbase/storefront-engine": "^0.3.10",
|
||||
"@fleetbase/dev-engine": "^0.2.3",
|
||||
"@fleetbase/iam-engine": "^0.0.12",
|
||||
"@fleetbase/fleetops-data": "^0.1.15",
|
||||
"@fleetbase/leaflet-routing-machine": "^3.2.16",
|
||||
"@ember/legacy-built-in-components": "^0.4.1",
|
||||
"@fortawesome/ember-fontawesome": "^0.4.1",
|
||||
@@ -142,9 +142,9 @@
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"@fleetbase/ember-core": "^0.2.4",
|
||||
"@fleetbase/ember-ui": "^0.2.11",
|
||||
"@fleetbase/fleetops-data": "^0.1.9"
|
||||
"@fleetbase/ember-core": "^0.2.11",
|
||||
"@fleetbase/ember-ui": "^0.2.17",
|
||||
"@fleetbase/fleetops-data": "^0.1.15"
|
||||
}
|
||||
},
|
||||
"prettier": {
|
||||
|
||||
3530
console/pnpm-lock.yaml
generated
3530
console/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -54,6 +54,9 @@ Router.map(function () {
|
||||
this.route('users', { path: '/:public_id/users' });
|
||||
});
|
||||
});
|
||||
this.route('schedule-monitor', function () {
|
||||
this.route('logs', { path: '/:id/logs' });
|
||||
});
|
||||
});
|
||||
});
|
||||
this.route('install');
|
||||
|
||||
@@ -3,22 +3,22 @@ import { setupRenderingTest } from '@fleetbase/console/tests/helpers';
|
||||
import { render } from '@ember/test-helpers';
|
||||
import { hbs } from 'ember-cli-htmlbars';
|
||||
|
||||
module('Integration | Component | locale-selector', function (hooks) {
|
||||
module('Integration | Component | console-wormhole', 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`<LocaleSelector />`);
|
||||
await render(hbs`<ConsoleWormhole />`);
|
||||
|
||||
assert.dom().hasText('');
|
||||
|
||||
// Template block usage:
|
||||
await render(hbs`
|
||||
<LocaleSelector>
|
||||
<ConsoleWormhole>
|
||||
template block text
|
||||
</LocaleSelector>
|
||||
</ConsoleWormhole>
|
||||
`);
|
||||
|
||||
assert.dom().hasText('template block text');
|
||||
@@ -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 | modals/validate-password', 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`<Modals::ValidatePassword />`);
|
||||
|
||||
assert.dom().hasText('');
|
||||
|
||||
// Template block usage:
|
||||
await render(hbs`
|
||||
<Modals::ValidatePassword>
|
||||
template block text
|
||||
</Modals::ValidatePassword>
|
||||
`);
|
||||
|
||||
assert.dom().hasText('template block text');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from '@fleetbase/console/tests/helpers';
|
||||
|
||||
module('Unit | Controller | console/admin/schedule-monitor/logs', function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
// TODO: Replace this with your real tests.
|
||||
test('it exists', function (assert) {
|
||||
let controller = this.owner.lookup('controller:console/admin/schedule-monitor/logs');
|
||||
assert.ok(controller);
|
||||
});
|
||||
});
|
||||
14
console/tests/unit/models/chat-log-test.js
Normal file
14
console/tests/unit/models/chat-log-test.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import { module, test } from 'qunit';
|
||||
|
||||
import { setupTest } from '@fleetbase/console/tests/helpers';
|
||||
|
||||
module('Unit | Model | chat log', 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('chat-log', {});
|
||||
assert.ok(model);
|
||||
});
|
||||
});
|
||||
14
console/tests/unit/models/custom-field-test.js
Normal file
14
console/tests/unit/models/custom-field-test.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import { module, test } from 'qunit';
|
||||
|
||||
import { setupTest } from '@fleetbase/console/tests/helpers';
|
||||
|
||||
module('Unit | Model | custom field', 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('custom-field', {});
|
||||
assert.ok(model);
|
||||
});
|
||||
});
|
||||
14
console/tests/unit/models/custom-field-value-test.js
Normal file
14
console/tests/unit/models/custom-field-value-test.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import { module, test } from 'qunit';
|
||||
|
||||
import { setupTest } from '@fleetbase/console/tests/helpers';
|
||||
|
||||
module('Unit | Model | custom field value', 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('custom-field-value', {});
|
||||
assert.ok(model);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from '@fleetbase/console/tests/helpers';
|
||||
|
||||
module('Unit | Route | console/admin/schedule-monitor', function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('it exists', function (assert) {
|
||||
let route = this.owner.lookup('route:console/admin/schedule-monitor');
|
||||
assert.ok(route);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from '@fleetbase/console/tests/helpers';
|
||||
|
||||
module('Unit | Route | console/admin/schedule-monitor/logs', function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('it exists', function (assert) {
|
||||
let route = this.owner.lookup('route:console/admin/schedule-monitor/logs');
|
||||
assert.ok(route);
|
||||
});
|
||||
});
|
||||
24
console/tests/unit/serializers/chat-channel-test.js
Normal file
24
console/tests/unit/serializers/chat-channel-test.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import { module, test } from 'qunit';
|
||||
|
||||
import { setupTest } from '@fleetbase/console/tests/helpers';
|
||||
|
||||
module('Unit | Serializer | chat channel', 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('chat-channel');
|
||||
|
||||
assert.ok(serializer);
|
||||
});
|
||||
|
||||
test('it serializes records', function (assert) {
|
||||
let store = this.owner.lookup('service:store');
|
||||
let record = store.createRecord('chat-channel', {});
|
||||
|
||||
let serializedRecord = record.serialize();
|
||||
|
||||
assert.ok(serializedRecord);
|
||||
});
|
||||
});
|
||||
24
console/tests/unit/serializers/chat-message-test.js
Normal file
24
console/tests/unit/serializers/chat-message-test.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import { module, test } from 'qunit';
|
||||
|
||||
import { setupTest } from '@fleetbase/console/tests/helpers';
|
||||
|
||||
module('Unit | Serializer | chat message', 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('chat-message');
|
||||
|
||||
assert.ok(serializer);
|
||||
});
|
||||
|
||||
test('it serializes records', function (assert) {
|
||||
let store = this.owner.lookup('service:store');
|
||||
let record = store.createRecord('chat-message', {});
|
||||
|
||||
let serializedRecord = record.serialize();
|
||||
|
||||
assert.ok(serializedRecord);
|
||||
});
|
||||
});
|
||||
24
console/tests/unit/serializers/chat-participant-test.js
Normal file
24
console/tests/unit/serializers/chat-participant-test.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import { module, test } from 'qunit';
|
||||
|
||||
import { setupTest } from '@fleetbase/console/tests/helpers';
|
||||
|
||||
module('Unit | Serializer | chat participant', 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('chat-participant');
|
||||
|
||||
assert.ok(serializer);
|
||||
});
|
||||
|
||||
test('it serializes records', function (assert) {
|
||||
let store = this.owner.lookup('service:store');
|
||||
let record = store.createRecord('chat-participant', {});
|
||||
|
||||
let serializedRecord = record.serialize();
|
||||
|
||||
assert.ok(serializedRecord);
|
||||
});
|
||||
});
|
||||
@@ -16,6 +16,7 @@ common:
|
||||
confirm: Confirm
|
||||
edit: Edit
|
||||
save: Save
|
||||
save-changes: Save Changes
|
||||
cancel: Cancel
|
||||
2fa-config: 2FA Config
|
||||
account: Account
|
||||
@@ -59,9 +60,25 @@ common:
|
||||
users: Users
|
||||
changelog: Changelog
|
||||
ok: OK
|
||||
select-file: Select File
|
||||
back: Back
|
||||
next: Next
|
||||
continue: Continue
|
||||
done: Done
|
||||
export: Export
|
||||
component:
|
||||
file:
|
||||
dropdown-label: File actions
|
||||
import-modal:
|
||||
loading-message: Processing import...
|
||||
drop-upload: Drop to upload
|
||||
invalid: Invalid
|
||||
ready-upload: ready for upload.
|
||||
upload-spreadsheets: Upload Spreadsheets
|
||||
drag-drop: Drag and drop spreadsheet files onto this dropzone
|
||||
button-text: or select spreadsheets to upload
|
||||
spreadsheets: spreadsheets
|
||||
upload-queue: Upload Queue
|
||||
dropzone:
|
||||
file: file
|
||||
drop-to-upload: Drop to upload
|
||||
@@ -173,6 +190,7 @@ auth:
|
||||
slow-connection-message: Experiencing connectivity issues.
|
||||
reset-password:
|
||||
success-message: Your password has been reset! Login to continue.
|
||||
invalid-verification-code: This reset password link is invalid or expired.
|
||||
title: Reset your password
|
||||
form:
|
||||
code:
|
||||
@@ -202,6 +220,22 @@ console:
|
||||
phone: Your phone number.
|
||||
photos: photos
|
||||
admin:
|
||||
schedule-monitor:
|
||||
schedule-monitor: Schedule Monitor
|
||||
task-logs-for: >-
|
||||
Task Logs For:
|
||||
showing-last-count: Showing last {count} logs
|
||||
name: Name
|
||||
type: Type
|
||||
timezone: Timezone
|
||||
last-started: Last Started
|
||||
last-finished: Last Finished
|
||||
last-failure: Last Failure
|
||||
date: Date
|
||||
memory: Memory
|
||||
runtime: Runtime
|
||||
output: Output
|
||||
no-output: No output
|
||||
config:
|
||||
database:
|
||||
title: Database Configuration
|
||||
|
||||
1283
database.mmd
1283
database.mmd
File diff suppressed because it is too large
Load Diff
@@ -69,6 +69,9 @@ services:
|
||||
MAIL_FROM_NAME: Fleetbase
|
||||
APP_NAME: Fleetbase
|
||||
LOG_CHANNEL: daily
|
||||
MODEL_CACHE_ENABLED: 'true'
|
||||
RESPONSE_CACHE_ENABLED: 'true'
|
||||
RESPONSE_CACHE_DRIVER: redis
|
||||
depends_on:
|
||||
- database
|
||||
- cache
|
||||
|
||||
@@ -12,7 +12,32 @@ server {
|
||||
send_timeout 600;
|
||||
index index.php;
|
||||
|
||||
# tweaks
|
||||
client_max_body_size 600M;
|
||||
client_body_buffer_size 1m;
|
||||
client_header_buffer_size 1k;
|
||||
large_client_header_buffers 4 16k;
|
||||
keepalive_timeout 2 2;
|
||||
|
||||
#gzip tweaks
|
||||
gzip on;
|
||||
gzip_disable "msie6";
|
||||
gzip_vary on;
|
||||
gzip_proxied any;
|
||||
gzip_comp_level 6;
|
||||
gzip_min_length 1100;
|
||||
gzip_buffers 16 8k;
|
||||
gzip_http_version 1.1;
|
||||
gzip_types text/plain application/javascript application/json application/x-javascript text/xml text/css;
|
||||
|
||||
#open file cache tweaks
|
||||
open_file_cache max=2000 inactive=20s;
|
||||
open_file_cache_valid 60s;
|
||||
open_file_cache_min_uses 5;
|
||||
open_file_cache_errors off;
|
||||
|
||||
location / {
|
||||
proxy_set_header Host $host;
|
||||
proxy_pass http://${NGINX_APPLICATION_HOSTNAME}:8000;
|
||||
}
|
||||
}
|
||||
|
||||
1
docs
Submodule
1
docs
Submodule
Submodule docs added at 6e1281f528
Submodule docs/api-reference deleted from 632bf97f1f
Submodule docs/guides deleted from 479ca400c4
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 2.3 MiB |
17770
erd.svg
17770
erd.svg
File diff suppressed because it is too large
Load Diff
|
Before Width: | Height: | Size: 794 KiB After Width: | Height: | Size: 1.3 MiB |
@@ -52,13 +52,13 @@ app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
|
||||
{{- define "helm.commonVariables" -}}
|
||||
- name: CACHE_URL
|
||||
value: $(REDIS_SERVICE_PORT)/1
|
||||
value: tcp://redis-service.{{ .Release.Namespace }}.svc.cluster.local/1
|
||||
- name: CACHE_DRIVER
|
||||
value: redis
|
||||
- name: SOCKETCLUSTER_PORT
|
||||
value: "80"
|
||||
- name: SOCKETCLUSTER_HOST
|
||||
value: $(SOCKETCLUSTER_SERVICE_HOST)
|
||||
value: socketcluster.{{ .Release.Namespace }}.svc.cluster.local
|
||||
{{- end }}
|
||||
{{/*
|
||||
Create the name of the service account to use
|
||||
|
||||
@@ -34,7 +34,11 @@ spec:
|
||||
- name: {{ .Chart.Name }}-httpd
|
||||
securityContext:
|
||||
{{- toYaml .Values.securityContext | nindent 12 }}
|
||||
{{- if .Values.gcp }}
|
||||
image: "{{ .Values.image.repository }}/app-httpd:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||
{{- else }}
|
||||
image: "{{ .Values.image.repository }}:app-httpd-{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||
{{- end }}
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
env:
|
||||
- name: NGINX_APPLICATION_HOSTNAME
|
||||
@@ -56,7 +60,11 @@ spec:
|
||||
- name: {{ .Chart.Name }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.securityContext | nindent 12 }}
|
||||
{{- if .Values.gcp }}
|
||||
image: "{{ .Values.image.repository }}/app:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||
{{- else }}
|
||||
image: "{{ .Values.image.repository }}:app-{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||
{{- end }}
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
envFrom:
|
||||
- secretRef:
|
||||
|
||||
@@ -35,7 +35,11 @@ spec:
|
||||
command: ["php", "artisan", "queue:work"]
|
||||
securityContext:
|
||||
{{- toYaml .Values.securityContext | nindent 12 }}
|
||||
{{- if .Values.gcp }}
|
||||
image: "{{ .Values.image.repository }}/events:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||
{{- else }}
|
||||
image: "{{ .Values.image.repository }}:events-{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||
{{- end }}
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
envFrom:
|
||||
- secretRef:
|
||||
@@ -93,7 +97,11 @@ spec:
|
||||
- name: scheduler
|
||||
securityContext:
|
||||
{{- toYaml .Values.securityContext | nindent 12 }}
|
||||
{{- if .Values.gcp }}
|
||||
image: "{{ .Values.image.repository }}/scheduler:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||
{{- else }}
|
||||
image: "{{ .Values.image.repository }}:scheduler-{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||
{{- end }}
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
envFrom:
|
||||
- secretRef:
|
||||
|
||||
@@ -10,7 +10,7 @@ metadata:
|
||||
helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
|
||||
annotations:
|
||||
"helm.sh/hook": pre-install,pre-upgrade
|
||||
"helm.sh/hook-weight": "0"
|
||||
"helm.sh/hook-weight": "20"
|
||||
"helm.sh/hook-delete-policy": before-hook-creation
|
||||
spec:
|
||||
template:
|
||||
@@ -28,8 +28,12 @@ spec:
|
||||
- name: deployment-job
|
||||
securityContext:
|
||||
{{- toYaml .Values.securityContext | nindent 10 }}
|
||||
{{- if .Values.gcp }}
|
||||
image: "{{ .Values.image.repository }}/app:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||
command: ["./deploy.sh"]
|
||||
{{- else }}
|
||||
image: "{{ .Values.image.repository }}:app-{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||
{{- end }}
|
||||
args: ["./deploy.sh"]
|
||||
env:
|
||||
{{- include "helm.commonVariables" . | nindent 12 }}
|
||||
envFrom:
|
||||
|
||||
@@ -44,7 +44,7 @@ spec:
|
||||
pathType: ImplementationSpecific
|
||||
backend:
|
||||
service:
|
||||
name: fleetbase-app
|
||||
name: {{ include "helm.fullname" . }}
|
||||
port:
|
||||
number: {{ $svcPort }}
|
||||
- host: {{ .Values.socketcluster_host }}
|
||||
|
||||
@@ -30,4 +30,3 @@ spec:
|
||||
port: 6379
|
||||
targetPort: 6379
|
||||
type: ClusterIP
|
||||
|
||||
|
||||
@@ -32,5 +32,4 @@ spec:
|
||||
- protocol: TCP
|
||||
port: 80
|
||||
targetPort: 8000
|
||||
type: ClusterIP
|
||||
|
||||
type: {{ .Values.service.type }}
|
||||
|
||||
Submodule packages/core-api updated: 0baf531ae4...05e927265c
Submodule packages/dev-engine updated: 64a379ce12...7bc431c6cd
Submodule packages/ember-core updated: e9f4dc4086...b17d74ddb8
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user