x-dev-15
xwashere 6 months ago
parent fa322e4166
commit a8f1d55078
No known key found for this signature in database
GPG Key ID: 042F8BFA1B0EF93B

@ -1,4 +1,4 @@
import { UserConfiguration, UserConfigTypeChecker, PostgresConfiguration } from 'ass';
import { UserConfiguration, UserConfigTypeChecker, PostgresConfiguration, MongoDBConfiguration } from 'ass';
import fs from 'fs-extra';
import { path } from '@tycrek/joint';
@ -108,15 +108,15 @@ export class UserConfig {
// * Optional database config(s)
if (config.database != null) {
// these both have the same schema so we can just check both
if (config.database.kind == 'mysql' || config.database.kind == 'postgres') {
if (config.database.kind == 'mysql' || config.database.kind == 'postgres' || config.database.kind == 'mongodb') {
if (config.database.options != undefined) {
if (!Checkers.sql.mySql.host(config.database.options.host)) throw new Error('Invalid database host');
if (!Checkers.sql.mySql.user(config.database.options.user)) throw new Error('Invalid databse user');
if (!Checkers.sql.mySql.password(config.database.options.password)) throw new Error('Invalid database password');
if (!Checkers.sql.mySql.database(config.database.options.database)) throw new Error('Invalid database');
if (config.database.kind == 'postgres') {
if (!Checkers.sql.postgres.port((config.database.options as PostgresConfiguration).port)) {
throw new Error("Invalid database port");
if (config.database.kind == 'postgres' || config.database.kind == 'mongodb') {
if (!Checkers.sql.postgres.port((config.database.options as PostgresConfiguration | MongoDBConfiguration).port)) {
throw new Error('Invalid database port');
}
}
} else throw new Error('Database options missing');

@ -16,6 +16,7 @@ import { MySQLDatabase } from './sql/mysql';
import { buildFrontendRouter } from './routers/_frontend';
import { DBManager } from './sql/database';
import { PostgreSQLDatabase } from './sql/postgres';
import { MongoDBDatabase } from './sql/mongodb';
/**
* Top-level metadata exports
@ -127,6 +128,9 @@ async function main() {
case 'postgres':
await DBManager.use(new PostgreSQLDatabase());
break;
case 'mongodb':
await DBManager.use(new MongoDBDatabase());
break;
}
} catch (err) { throw new Error(`Failed to configure SQL`); }
} else { // default to json database

@ -26,6 +26,7 @@ const PATHS = {
const DBNAMES = {
'mysql': 'MySQL',
'postgres': 'PostgreSQL',
'mongodb': 'MongoDB',
'json': 'JSON'
};

@ -12,6 +12,7 @@ import { DBManager } from '../sql/database';
import { JSONDatabase } from '../sql/json';
import { MySQLDatabase } from '../sql/mysql';
import { PostgreSQLDatabase } from '../sql/postgres';
import { MongoDBDatabase } from '../sql/mongodb';
const router = Router({ caseSensitive: true });
@ -41,6 +42,9 @@ router.post('/setup', BodyParserJson(), async (req, res) => {
case 'postgres':
await DBManager.use(new PostgreSQLDatabase());
break;
case 'mongodb':
await DBManager.use(new MongoDBDatabase());
break;
}
}

@ -0,0 +1,238 @@
import { AssFile, AssUser, MongoDBConfiguration, NID, UploadToken } from 'ass';
import { UserConfig } from '../UserConfig';
import { Database, DatabaseTable, DatabaseValue } from './database';
import mongoose, { Model, Mongoose, Schema } from 'mongoose';
import { log } from '../log';
interface TableVersion {
name: string;
version: number;
}
const VERSIONS_SCHEMA = new Schema<TableVersion>({
name: String,
version: Number
});
interface MongoSchema<T> {
id: NID,
data: T
}
const FILE_SCHEMA = new Schema<MongoSchema<AssFile>>({
id: String,
data: {
fakeid: String,
fileKey: String,
filename: String,
mimetype: String,
save: {
local: String,
s3: Boolean // this will break if it gets the url object
// but im so fucking tired of this, were just
// going to keep it like this until it becomes
// a problem
},
sha256: String,
size: Number,
timestamp: String,
uploader: String
}
});
const TOKEN_SCHEMA = new Schema<MongoSchema<UploadToken>>({
id: String,
data: {
id: String,
token: String,
hint: String
}
});
const USER_SCHEMA = new Schema<MongoSchema<AssUser>>({
id: String,
data: {
id: String,
username: String,
password: String,
admin: Boolean,
tokens: [ String ],
files: [ String ],
meta: {
type: String,
get: (v: string) => JSON.parse(v),
set: (v: string) => JSON.stringify(v)
}
}
});
/**
* database adapter for mongodb
*/
export class MongoDBDatabase implements Database {
private _client: Mongoose;
// mongoose models
private _versionModel: Model<TableVersion>;
private _fileModel: Model<MongoSchema<AssFile>>;
private _tokenModel: Model<MongoSchema<UploadToken>>;
private _userModel: Model<MongoSchema<AssUser>>;
private _validateConfig(): string | undefined {
// make sure the configuration exists
if (!UserConfig.ready) return 'User configuration not ready';
if (typeof UserConfig.config.database != 'object') return 'MongoDB configuration missing';
if (UserConfig.config.database.kind != 'mongodb') return 'Database not set to MongoDB, but MongoDB is in use, something has gone terribly wrong';
if (typeof UserConfig.config.database.options != 'object') return 'MongoDB configuration missing';
let config = UserConfig.config.database.options;
// check the postgres config
const checker = (val: string) => val != null && val !== '';
const issue =
!checker(config.host) ? 'Missing MongoDB Host'
: !checker(config.user) ? 'Missing MongoDB User'
: !checker(config.password) ? 'Missing MongoDB Password'
: !checker(config.database) ? 'Missing MongoDB Database'
// ! Blame VS Code for this weird indentation
: undefined;
return issue;
}
open(): Promise<void> {
return new Promise(async (resolve, reject) => {
try {
// validate config
let configError = this._validateConfig();
if (configError != null) throw new Error(configError);
let options = UserConfig.config.database!.options! as MongoDBConfiguration;
// connect
log.info('MongoDB', `connecting to ${options.host}:${options.port}`);
this._client = await mongoose.connect(`mongodb://${options.user}:${options.password}@${options.host}:${options.port}/${options.database}`);
log.success('MongoDB', 'ok');
resolve();
} catch (err) {
log.error('MongoDB', 'failed to connect');
console.error(err);
reject(err);
}
});
}
close(): Promise<void> {
return new Promise(async (resolve, reject) => {
try {
// gracefully disconnect
await this._client.disconnect();
resolve();
} catch (err) {
log.error('MongoDB', 'failed to disconnect');
console.error(err);
reject(err);
}
});
}
configure(): Promise<void> {
return new Promise(async (resolve, reject) => {
try {
this._versionModel = this._client.model('assversions', VERSIONS_SCHEMA);
this._fileModel = this._client.model('assfiles', FILE_SCHEMA);
this._tokenModel = this._client.model('asstokens', TOKEN_SCHEMA);
this._userModel = this._client.model('assusers', USER_SCHEMA);
// theres only one version right now so we dont need to worry about anything, just adding the version thingies if they arent there
let versions = await this._versionModel.find().exec()
.then(res => res.reduce((obj, doc) => obj.set(doc.name, doc.version), new Map<string, number>()));
for (let [table, version] of [['assfiles', 1], ['asstokens', 1], ['assusers', 1]] as [string, number][]) {
if (!versions.has(table)) {
// set the version
new this._versionModel({
name: table,
version: version
}).save();
versions.set(table, version);
}
}
resolve();
} catch (err) {
log.error('MongoDB', 'failed to configure');
console.error(err);
reject(err);
}
});
}
put(table: DatabaseTable, key: string, data: DatabaseValue): Promise<void> {
return new Promise(async (resolve, reject) => {
try {
const models = {
assfiles: this._fileModel,
assusers: this._userModel,
asstokens: this._tokenModel
};
await new models[table]({
id: key,
data: data
}).save();
resolve();
} catch (err) {
reject(err);
}
});
}
get(table: DatabaseTable, key: string): Promise<DatabaseValue | undefined> {
return new Promise(async (resolve, reject) => {
try {
const models = {
assfiles: this._fileModel,
assusers: this._userModel,
asstokens: this._tokenModel
};
// @ts-ignore
// typescript cant infer this but it is 100% correct
// no need to worry :>
let result = await models[table].find({
id: key
}).exec();
resolve(result.length ? result[0].data : void 0);
} catch (err) {
reject(err);
}
});
}
getAll(table: DatabaseTable): Promise<{ [index: string]: DatabaseValue; }> {
return new Promise(async (resolve, reject) => {
try {
const models = {
assfiles: this._fileModel,
assusers: this._userModel,
asstokens: this._tokenModel
};
// more ts-ignore!
// @ts-ignore
let result = await models[table].find({}).exec() // @ts-ignore
.then(res => res.reduce((obj, doc) => (obj[doc.id] = doc.data, obj), {}));
resolve(result);
} catch (err) {
reject(err);
}
});
}
};

@ -19,7 +19,7 @@ export class PostgreSQLDatabase implements Database {
// make sure the configuration exists
if (!UserConfig.ready) return 'User configuration not ready';
if (typeof UserConfig.config.database != 'object') return 'PostgreSQL configuration missing';
if (UserConfig.config.database.kind != "postgres") return 'Database not set to PostgreSQL, but PostgreSQL is in use, something has gone terribly wrong';
if (UserConfig.config.database.kind != 'postgres') return 'Database not set to PostgreSQL, but PostgreSQL is in use, something has gone terribly wrong';
if (typeof UserConfig.config.database.options != 'object') return 'PostgreSQL configuration missing';
let config = UserConfig.config.database.options;
@ -35,7 +35,6 @@ export class PostgreSQLDatabase implements Database {
: undefined;
return issue;
}
public open(): Promise<void> {

12
common/types.d.ts vendored

@ -52,8 +52,8 @@ declare module 'ass' {
}
interface DatabaseConfiguration {
kind: 'mysql' | 'postgres' | 'json';
options?: MySQLConfiguration | PostgresConfiguration;
kind: 'mysql' | 'postgres' | 'json' | 'mongodb';
options?: MySQLConfiguration | PostgresConfiguration | MongoDBConfiguration;
}
interface MySQLConfiguration {
@ -71,6 +71,14 @@ declare module 'ass' {
database: string;
}
interface MongoDBConfiguration {
host: string;
port: number;
user: string;
password: string;
database: string;
}
/**
* rate limiter configuration
* @since 0.15.0

@ -56,6 +56,13 @@ document.addEventListener('DOMContentLoaded', () => {
pgsqlPassword: document.querySelector('#pgsql-password') as SlInput,
pgsqlDatabase: document.querySelector('#pgsql-database') as SlInput,
mongoDBTab: document.querySelector('#mongodb-tab') as SlTab,
mongoDBHost: document.querySelector('#mongodb-host') as SlInput,
mongoDBPort: document.querySelector('#mongodb-port') as SlInput,
mongoDBUser: document.querySelector('#mongodb-user') as SlInput,
mongoDBPassword: document.querySelector('#mongodb-password') as SlInput,
mongoDBDatabase: document.querySelector('#mongodb-database') as SlInput,
userUsername: document.querySelector('#user-username') as SlInput,
userPassword: document.querySelector('#user-password') as SlInput,
@ -127,7 +134,20 @@ document.addEventListener('DOMContentLoaded', () => {
database: Elements.pgsqlDatabase.value
}
}
};
}
} else if (Elements.mongoDBTab.active) {
if (Elements.mongoDBHost.value != null && Elements.mongoDBHost.value != '') {
config.database = {
kind: 'mongodb',
options: {
host: Elements.mongoDBHost.value,
port: parseInt(Elements.mongoDBPort.value),
user: Elements.mongoDBUser.value,
password: Elements.mongoDBPassword.value,
database: Elements.mongoDBDatabase.value
}
}
}
}
// append rate limit config, if specified

@ -51,6 +51,7 @@
"fs-extra": "^11.1.1",
"luxon": "^3.4.3",
"memorystore": "^1.6.7",
"mongoose": "^8.0.0",
"mysql2": "^3.6.2",
"node-vibrant": "^3.1.6",
"pg": "^8.11.3",

@ -71,6 +71,9 @@ dependencies:
memorystore:
specifier: ^1.6.7
version: 1.6.7
mongoose:
specifier: ^8.0.0
version: 8.0.0
mysql2:
specifier: ^3.6.2
version: 3.6.2
@ -1205,6 +1208,12 @@ packages:
- supports-color
dev: false
/@mongodb-js/saslprep@1.1.1:
resolution: {integrity: sha512-t7c5K033joZZMspnHg/gWPE4kandgc2OxE74aYOtGKfgB9VPuVJPix0H6fhmm2erj5PBJ21mqcx34lpIGtUCsQ==}
dependencies:
sparse-bitfield: 3.0.3
dev: false
/@nodelib/fs.scandir@2.1.5:
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
engines: {node: '>= 8'}
@ -1930,6 +1939,17 @@ packages:
resolution: {integrity: sha512-I3pkr8j/6tmQtKV/ZzHtuaqYSQvyjGRKH4go60Rr0IDLlFxuRT5V32uvB1mecM5G1EVAUyF/4r4QZ1GHgz+mxA==}
dev: false
/@types/webidl-conversions@7.0.2:
resolution: {integrity: sha512-uNv6b/uGRLlCVmelat2rA8bcVd3k/42mV2EmjhPh6JLkd35T5bgwR/t6xy7a9MWhd9sixIeBUzhBenvk3NO+DQ==}
dev: false
/@types/whatwg-url@8.2.2:
resolution: {integrity: sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==}
dependencies:
'@types/node': 20.8.9
'@types/webidl-conversions': 7.0.2
dev: false
/@xoi/gps-metadata-remover@1.1.2(@babel/core@7.23.2):
resolution: {integrity: sha512-QeGcEvlesS+cXwfao14kdLI2zHJk3vppKSEbpbiNP1abx45P8HWqGEWhgF71bKlnCSW8a7b4RNDNa4mj1aHPMA==}
dependencies:
@ -2190,6 +2210,11 @@ packages:
update-browserslist-db: 1.0.13(browserslist@4.22.1)
dev: false
/bson@6.2.0:
resolution: {integrity: sha512-ID1cI+7bazPDyL9wYy9GaQ8gEEohWvcUl/Yf0dIdutJxnmInEEyCsb4awy/OiBfall7zBA179Pahi3vCdFze3Q==}
engines: {node: '>=16.20.1'}
dev: false
/buffer-equal@0.0.1:
resolution: {integrity: sha512-RgSV6InVQ9ODPdLWJ5UAqBqJBOg370Nz6ZQtRzpt6nUjc8v0St97uJ4PYC6NztqIScrAXafKM3mZPMygSe1ggA==}
engines: {node: '>=0.4.0'}
@ -3344,6 +3369,11 @@ packages:
promise: 7.3.1
dev: false
/kareem@2.5.1:
resolution: {integrity: sha512-7jFxRVm+jD+rkq3kY0iZDJfsO2/t4BBPeEb2qKn2lR/9KhuksYk5hxzfRYWMPV8P/x2d0kHD306YyWLzjjH+uA==}
engines: {node: '>=12.0.0'}
dev: false
/lilconfig@2.1.0:
resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==}
engines: {node: '>=10'}
@ -3459,6 +3489,10 @@ packages:
engines: {node: '>= 0.6'}
dev: false
/memory-pager@1.5.0:
resolution: {integrity: sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==}
dev: false
/memorystore@1.6.7:
resolution: {integrity: sha512-OZnmNY/NDrKohPQ+hxp0muBcBKrzKNtHr55DbqSx9hLsYVNnomSAMRAtI7R64t3gf3ID7tHQA7mG4oL3Hu9hdw==}
engines: {node: '>=0.10'}
@ -3567,6 +3601,81 @@ packages:
hasBin: true
dev: false
/mongodb-connection-string-url@2.6.0:
resolution: {integrity: sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==}
dependencies:
'@types/whatwg-url': 8.2.2
whatwg-url: 11.0.0
dev: false
/mongodb@6.2.0:
resolution: {integrity: sha512-d7OSuGjGWDZ5usZPqfvb36laQ9CPhnWkAGHT61x5P95p/8nMVeH8asloMwW6GcYFeB0Vj4CB/1wOTDG2RA9BFA==}
engines: {node: '>=16.20.1'}
peerDependencies:
'@aws-sdk/credential-providers': ^3.188.0
'@mongodb-js/zstd': ^1.1.0
gcp-metadata: ^5.2.0
kerberos: ^2.0.1
mongodb-client-encryption: '>=6.0.0 <7'
snappy: ^7.2.2
socks: ^2.7.1
peerDependenciesMeta:
'@aws-sdk/credential-providers':
optional: true
'@mongodb-js/zstd':
optional: true
gcp-metadata:
optional: true
kerberos:
optional: true
mongodb-client-encryption:
optional: true
snappy:
optional: true
socks:
optional: true
dependencies:
'@mongodb-js/saslprep': 1.1.1
bson: 6.2.0
mongodb-connection-string-url: 2.6.0
dev: false
/mongoose@8.0.0:
resolution: {integrity: sha512-PzwkLgm1Jhj0NQdgGfnFsu0QP9V1sBFgbavEgh/IPAUzKAagzvEhuaBuAQOQGjczVWnpIU9tBqyd02cOTgsPlA==}
engines: {node: '>=16.20.1'}
dependencies:
bson: 6.2.0
kareem: 2.5.1
mongodb: 6.2.0
mpath: 0.9.0
mquery: 5.0.0
ms: 2.1.3
sift: 16.0.1
transitivePeerDependencies:
- '@aws-sdk/credential-providers'
- '@mongodb-js/zstd'
- gcp-metadata
- kerberos
- mongodb-client-encryption
- snappy
- socks
- supports-color
dev: false
/mpath@0.9.0:
resolution: {integrity: sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==}
engines: {node: '>=4.0.0'}
dev: false
/mquery@5.0.0:
resolution: {integrity: sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==}
engines: {node: '>=14.0.0'}
dependencies:
debug: 4.3.4
transitivePeerDependencies:
- supports-color
dev: false
/ms@2.0.0:
resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
dev: false
@ -4466,6 +4575,11 @@ packages:
resolution: {integrity: sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==}
dev: false
/punycode@2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
dev: false
/qr-creator@1.0.0:
resolution: {integrity: sha512-C0cqfbS1P5hfqN4NhsYsUXePlk9BO+a45bAQ3xLYjBL3bOIFzoVEjs79Fado9u9BPBD3buHi3+vY+C8tHh4qMQ==}
dev: false
@ -4716,6 +4830,10 @@ packages:
object-inspect: 1.13.1
dev: false
/sift@16.0.1:
resolution: {integrity: sha512-Wv6BjQ5zbhW7VFefWusVP33T/EM0vYikCaQ2qR8yULbsilAT8/wQaXvuQ3ptGLpoKx+lihJE3y2UTgKDyyNHZQ==}
dev: false
/signal-exit@3.0.7:
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
dev: false
@ -4743,6 +4861,12 @@ packages:
engines: {node: '>=0.10.0'}
dev: false
/sparse-bitfield@3.0.3:
resolution: {integrity: sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==}
dependencies:
memory-pager: 1.5.0
dev: false
/split2@4.2.0:
resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
engines: {node: '>= 10.x'}
@ -5007,6 +5131,13 @@ packages:
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
dev: false
/tr46@3.0.0:
resolution: {integrity: sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==}
engines: {node: '>=12'}
dependencies:
punycode: 2.3.1
dev: false
/ts-interface-checker@0.1.13:
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
dev: false
@ -5131,6 +5262,19 @@ packages:
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
dev: false
/webidl-conversions@7.0.0:
resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
engines: {node: '>=12'}
dev: false
/whatwg-url@11.0.0:
resolution: {integrity: sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==}
engines: {node: '>=12'}
dependencies:
tr46: 3.0.0
webidl-conversions: 7.0.0
dev: false
/whatwg-url@5.0.0:
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
dependencies:

@ -65,6 +65,20 @@ block content
h3.setup-text-item-title Database
sl-input#pgsql-database(type='text' placeholder='assdb' clearable): sl-icon(slot='prefix' name='fas-database' library='fa')
//- * MongoDB
sl-tab#mongodb-tab(slot='nav' panel='mongodb') MongoDB
sl-tab-panel(name='mongodb')
h3.setup-text-item-title Host
sl-input#mongodb-host(type='text' placeholder='mongo.example.com' clearable): sl-icon(slot='prefix' name='fas-server' library='fa')
h3.setup-text-item-title Port
sl-input#mongodb-port(type='number' placeholder='27017' min='1' max='65535' no-spin-buttons clearable): sl-icon(slot='prefix' name='fas-number' library='fa')
h3.setup-text-item-title User
sl-input#mongodb-user(type='text' placeholder='mongo' clearable): sl-icon(slot='prefix' name='fas-user' library='fa')
h3.setup-text-item-title Password
sl-input#mongodb-password(type='password' placeholder='super-secure' clearable): sl-icon(slot='prefix' name='fas-lock' library='fa')
h3.setup-text-item-title Database
sl-input#mongodb-database(type='text' placeholder='assdb' clearable): sl-icon(slot='prefix' name='fas-database' library='fa')
//- * S3
h2.setup-text-section-header.mt-4 S3 #[span.setup-text-optional optional]
.setup-panel

Loading…
Cancel
Save