Feat: Add system info (Client + Server metrics) (#2311)

* Add ststem info to user options extensions - Add UIWindowSystemInfo, add ui sections for client and server, add basic getClientinfo function

* Fix typo

* Replace accidentally deleted es.js file

* Refactor client information to be consistant with project standard

* Complete Client information in ststem information window

* Remove console logs

* Add basic api functionality for getting server system information

* Structure return data from system server information endpoint | Add copyright to UIWindowSystemInfo

* Add function to format server system api data | Add loading element to server container while waiting for data | Complete System Information

* fix: disallow non admin for backend + move to extensions

---------

Co-authored-by: Daniel Salazar <daniel.salazar@puter.com>
This commit is contained in:
Lui Duarte
2026-01-27 10:29:02 -08:00
committed by GitHub
co-authored by Daniel Salazar
parent bbe1242eff
commit bbe6f9dc27
18 changed files with 683 additions and 1 deletions
+5
View File
@@ -0,0 +1,5 @@
{
"allowedUsernames": [
"puter"
]
}
+63
View File
@@ -0,0 +1,63 @@
import fs from 'fs/promises';
import os from 'os';
const { Controller, Get, ExtensionController } = extension.import('extensionController');
@Controller('/serverInfo', [...config.allowedUsernames])
class ServerInfoController extends ExtensionController {
@Get('', { subdomain: 'api' })
async getServerInfo (req, res) {
const osData = {
platform: os.platform(),
type: os.type(),
release: os.release(),
pretty: `${os.type()} ${os.release()}`,
};
const cpus = os.cpus();
const cpuData = {
model: cpus[0]?.model || 'Unknown',
cores: cpus.length,
};
const ramData = {
total: os.totalmem(),
free: os.freemem(),
totalGB: (os.totalmem() / 1073741824).toFixed(2),
freeGB: (os.freemem() / 1073741824).toFixed(2),
};
const uptimeSeconds = os.uptime();
const uptimeData = {
seconds: uptimeSeconds,
days: Math.floor(uptimeSeconds / 86400),
hours: Math.floor((uptimeSeconds % 86400) / 3600),
minutes: Math.floor((uptimeSeconds % 3600) / 60),
pretty: `${Math.floor(uptimeSeconds / 86400)}d ${Math.floor((uptimeSeconds % 86400) / 3600)}h ${Math.floor((uptimeSeconds % 3600) / 60)}m`,
};
let diskData = { total: 'N/A', free: 'N/A', used: 'N/A' };
try {
const stats = await fs.statfs('/');
const totalGB = (stats.blocks * stats.bsize / 1073741824);
const freeGB = (stats.bfree * stats.bsize / 1073741824);
const usedGB = (totalGB - freeGB).toFixed(2);
diskData = { total: totalGB.toFixed(2), free: freeGB.toFixed(2), used: usedGB };
} catch ( err ) {
console.error('Disk stats error:', err);
}
const response = {
os: osData,
cpu: cpuData,
ram: ramData,
uptime: uptimeData,
disk: diskData,
loadavg: os.loadavg(),
hostname: os.hostname(),
};
res.json(response);
}
}
(new ServerInfoController()).registerRoutes();
+11
View File
@@ -0,0 +1,11 @@
{
"name": "@heyputer/server-info-extension",
"main": "index.js",
"type": "module",
"scripts": {
"postinstall": "tsc --noCheck"
},
"devDependencies": {
"typescript": "^5.9.3"
}
}
+28
View File
@@ -0,0 +1,28 @@
{
"compilerOptions": {
"target": "ES2024",
"module": "nodenext",
"moduleResolution": "nodenext",
"strict": true,
"forceConsistentCasingInFileNames": true,
"skipLibCheck": true,
"sourceMap": true,
"noEmitOnError": true,
"noImplicitAny": false,
"allowJs": true,
"checkJs": false,
},
"include": [
"./**/*.ts",
"./**/*.d.ts"
],
"exclude": [
"**/*.test.ts",
"**/*.spec.ts",
"**/test/**",
"**/tests/**",
"node_modules",
"dist",
"*.js"
]
}
+1
View File
@@ -0,0 +1 @@
import '../api.js';
+295
View File
@@ -0,0 +1,295 @@
/**
* Copyright (C) 2024-present Puter Technologies Inc.
*
* This file is part of Puter.
*
* Puter is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import UIWindow from './UIWindow.js';
import * as utils from '../../../puter-js/src/lib/utils.js';
const triggerRefreshBtnAnimation = ($btn) => {
const $icon = $btn.find('.update-usage-details-icon');
const icon = $icon[0];
const clone = icon.cloneNode(true);
// Cloned node required to get animation to play on refresh
icon.parentNode.replaceChild(clone, icon);
};
// Leverage User-Agent Client Hints API to request user browser information
async function getClientInfo () {
let clientInfo = [];
// Get browser & OS info
if ( navigator.userAgentData ) {
const uaData = await navigator.userAgentData.getHighEntropyValues([
'platform', 'platformVersion', 'model', 'fullVersionList',
]);
const browser = uaData.brands?.[0]?.brand || 'Unknown';
const browserVersion = uaData.brands?.[0]?.version || 'Unknown';
const os = uaData.platform || 'Unknown';
const osVersion = uaData.platformVersion || 'Unknown';
clientInfo.push ({
key: 'browser',
icon: 'system-info-browser.svg',
i18n_key: 'browser',
title: i18n('browser'),
value: `${browser} ${browserVersion}`,
},
{
key: 'os',
icon: 'system-info-os.svg',
i18n_key: 'system-info-os',
title: i18n('os'),
value: `${os} ${osVersion}`,
});
} else {
// Fallback for older browsers
const userAgent = navigator.userAgent;
let os = 'Unknown';
if ( /Win/.test(userAgent) ) os = 'Windows';
else if ( /Mac/.test(userAgent) ) os = 'macOS';
else if ( /Linux/.test(userAgent) ) os = 'Linux';
else if ( /Android/.test(userAgent) ) os = 'Android';
else if ( /iPhone|iPad|iPod/.test(userAgent) ) os = 'iOS';
clientInfo.push({
key: 'os',
icon: 'system-info-os.svg',
i18n_key: 'os',
title: i18n('os'),
value: os,
});
}
// Get hardware info
const cpuCores = navigator.hardwareConcurrency || 'Unknown';
const ram = navigator.deviceMemory ? `${navigator.deviceMemory} GB (approx)` : 'Unknown';
clientInfo.push({
key: 'cpu_cores',
icon: 'system-info-cpu.svg',
i18n_key: 'cpu_cores',
title: i18n('cpu_cores'),
value: `${cpuCores} cores`,
},
{
key: 'ram',
icon: 'system-info-ram.svg',
i18n_key: 'ram',
title: i18n('ram'),
value: ram,
});
// Get screen info
const screenResolution = `${window.screen.width}x${window.screen.height}`;
const pixelRatio = window.devicePixelRatio;
const colorDepth = window.screen.colorDepth;
clientInfo.push({
key: 'screen_resolution',
icon: 'system-info-screen.svg',
i18n_key: 'screen_resolution',
title: i18n('screen_resolution'),
value: screenResolution,
},
{
key: 'pixel_ratio',
icon: 'system-info-pixel.svg',
i18n_key: 'pixel_ratio',
title: i18n('pixel_ratio'),
value: `${pixelRatio}x`,
},
{
key: 'color_depth',
icon: 'system-info-color.svg',
i18n_key: 'color_depth',
title: i18n('color_depth'),
value: `${colorDepth} bits`,
});
return clientInfo;
}
async function getServerInfo (options = {}) {
const APIOrigin = window.puter?.APIOrigin;
const authToken = window.puter?.authToken;
return new Promise((resolve, reject) => {
const xhr = utils.initXhr('/serverInfo', APIOrigin, authToken, 'get');
utils.setupXhrEventHandlers(xhr, options.success, options.error, resolve, reject);
xhr.send();
});
}
async function getServerInfoFormatted () {
try {
const rawServerData = await getServerInfo();
// Map raw data to render-ready array
return [
{
key: 'os',
icon: 'system-info-os.svg',
i18n_key: 'os',
title: i18n('os'),
value: rawServerData.os?.pretty || `${rawServerData.os?.type || 'Unknown'} ${rawServerData.os?.release || ''}`,
},
{
key: 'cpu',
icon: 'system-info-cpu.svg',
i18n_key: 'cpu',
title: i18n('cpu'),
value: `${rawServerData.cpu?.model || 'Unknown'} (${rawServerData.cpu?.cores || 0} cores)`,
},
{
key: 'ram',
icon: 'system-info-ram.svg',
i18n_key: 'ram',
title: i18n('ram'),
value: `${rawServerData.ram?.freeGB || 0} Free / ${rawServerData.ram?.totalGB || 0} GB`,
},
{
key: 'disk_storage',
icon: 'system-info-storage.svg',
i18n_key: 'disk_storage',
title: i18n('disk_storage'),
value: `${rawServerData.disk?.used || 0} Used / ${rawServerData.disk?.total || 0} GB`,
},
{
key: 'uptime',
icon: 'system-info-time.svg',
i18n_key: 'uptime',
title: i18n('uptime'),
value: rawServerData.uptime?.pretty || 'N/A',
},
];
} catch ( err ) {
console.error('Failed to fetch server info:', err);
return [];
}
}
function renderSystemInfo ( information ) {
let html = '';
for ( const info of information ) {
html += `<div class="systeminfo-item">
<h3 class='systeminfo-title'>${info.title}</h3>
<div class='systeminfo-value'>
<img src='${window.icons[info.icon]}' class="systeminfo-icon" alt='${info.i18n} image'>
${info.value}
</div>
</div>`;
}
return html;
}
async function UIWindowSystemInfo (options) {
return new Promise(async (resolve) => {
// Build client & Server containers & headers
const h = `<div class="systeminfo-container">
<div class="clientinfo-container">
<h1>${i18n('client_information')}
<button class="update-usage-details client-btn" style="float:right;">
<svg class="update-usage-details-icon" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M8 3a5 5 0 1 0 4.546 2.914.5.5 0 0 1 .908-.417A6 6 0 1 1 8 2z"/>
<path d="M8 4.466V.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384L8.41 4.658A.25.25 0 0 1 8 4.466"/>
</svg>
</button>
</h1>
<div class="clientinfo-content"></div>
</div>
<div class="serverinfo-container">
<h1>${i18n('server_information')}
<button class="update-usage-details server-btn" style="float:right;">
<svg class="update-usage-details-icon" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M8 3a5 5 0 1 0 4.546 2.914.5.5 0 0 1 .908-.417A6 6 0 1 1 8 2z"/>
<path d="M8 4.466V.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384L8.41 4.658A.25.25 0 0 1 8 4.466"/>
</svg>
</button>
</h1>
<div class="serverinfo-content"></div>
</div>
</div>`;
const el_window = await UIWindow({
title: 'System Information',
app: 'System Information',
single_instance: true,
icon: null,
uid: null,
is_dir: false,
body_content: h,
has_head: true,
selectable_body: false,
allow_context_menu: false,
is_resizable: true,
is_droppable: false,
init_center: true,
allow_native_ctxmenu: true,
allow_user_select: true,
backdrop: false,
width: 560,
height: 540,
dominant: true,
show_in_taskbar: true,
draggable_body: false,
body_css: {
width: 'initial',
height: 'calc(100% - 30px)',
overflow: 'auto',
},
...options?.window_options ?? {},
});
// Scope jQuery to this window
const $win = $(el_window);
// Inject client info on launch
const clientInfo = await getClientInfo();
const clientInfohtml = renderSystemInfo(clientInfo);
$win.find('.clientinfo-content').html(clientInfohtml);
// Inject server info on launch
$win.find('.serverinfo-content').html('<p style="font-weight:500;font-size:14px;color:#3C4963">Loading server info...</p>');
const serverInfo = await getServerInfoFormatted();
const serverInfohtml = renderSystemInfo(serverInfo);
$win.find('.serverinfo-content').html(serverInfohtml);
// Spin both reset buttons once on launch
const $icons = $win.find('.update-usage-details-icon');
$icons.addClass('spin-once');
// Refresh button onclick event
$win.on('click', '.update-usage-details', async function () {
if ( $(this).hasClass('client-btn') ) {
triggerRefreshBtnAnimation($(this));
const clientInfo = await getClientInfo();
const clientInfohtml = renderSystemInfo(clientInfo);
$win.find('.clientinfo-content').html(clientInfohtml);
} else {
triggerRefreshBtnAnimation($(this));
const serverInfo = await getServerInfoFormatted();
const serverInfohtml = renderSystemInfo(serverInfo);
$win.find('.serverinfo-content').html(serverInfohtml);
}
});
resolve(el_window);
});
}
export default UIWindowSystemInfo;
+84 -1
View File
@@ -1200,7 +1200,7 @@ span.header-sort-icon img {
.window-body {
width: 100%;
height: calc(100% - 77px);
height: 100%;
background-color: white;
overflow: auto;
}
@@ -2952,6 +2952,89 @@ label {
}
/*****************************************************
* System Information
*****************************************************/
.systeminfo-container {
padding: 20px;
display: flex;
flex-direction: column;
gap: 20px;
background-color: #f9f9f9;
}
.serverinfo-container,
.clientinfo-container {
padding: 20px;
background-color: #ffffff;
border: 1px solid #cccccc8f;
border-radius: 4px;
}
.serverinfo-container h1,
.clientinfo-container h1 {
font-size: 24px;
margin-bottom: 20px;
border-bottom: 1px solid #e0e0e0;
padding-bottom: 10px;
padding-left: 5px;
font-weight: 500;
}
.update-usage-details-icon {
transform-origin: center;
transform-box: fill-box;
}
/* For refresh button animation */
.spin-once { animation: spin-once 1s linear; }
@keyframes spin-once {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.clientinfo-content,
.serverinfo-content {
display: flex;
flex-wrap: wrap;
gap: 16px;
}
.systeminfo-item {
flex: 1 1 45%; /* Grow, shrink, min width 45% */
min-width: 150px; /* Prevents items from getting too small */
box-sizing: border-box;
display: flex;
flex-direction: column;
gap: 5px
}
.systeminfo-value {
display: flex;
justify-content: flex-start;
align-items: center;
gap: 10px;
}
.systeminfo-title {
font-weight: 500;
font-size: 14px;
color: #3C4963;
margin: 0;
}
.systeminfo-value {
color: #3C4963;
font-size: 13px;
}
.systeminfo-icon {
width: 20px;
height: 20px;
}
/*****************************************************
* Tooltip
*****************************************************/
@@ -17,10 +17,29 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import UIWindowSystemInfo from '../UI/UIWindowSystemInfo.js';
console.debug('[puter] modify-user-options-menu loaded');
$(window).on('ctxmenu-will-open', (event) => {
if ( event.detail.options?.id === 'user-options-menu' ) {
// Define array of new menu items
const newMenuItems = [
// System Information window
{
id: 'system_information',
html: 'System Information',
html_active: 'System Information',
action: async function () {
try {
console.debug('[puter] System Information click');
await UIWindowSystemInfo();
console.debug('[puter] System Information opened');
} catch (e) {
console.error('[puter] System Information failed', e);
}
},
},
// Separator
'-',
// 'Developer', opens developer site in new tab
+14
View File
@@ -36,6 +36,8 @@ const en = {
auto_arrange: 'Auto Arrange',
background: 'Background',
browse: 'Browse',
browser: 'Browser',
browser_version: 'Browser Version',
captcha_required: 'Please complete the CAPTCHA verification',
cancel: 'Cancel',
center: 'Center',
@@ -47,6 +49,7 @@ const en = {
change_password: 'Change Password',
change_ui_colors: 'Change UI Colors',
change_username: 'Change Username',
color_depth: 'Color Depth',
clock_visibility: 'Clock Visibility',
close: 'Close',
close_all_windows: 'Close All Windows',
@@ -83,6 +86,8 @@ const en = {
copying: 'Copying',
copying_file: 'Copying %%',
cover: 'Cover',
cpu_cores: 'CPU Cores',
cpu: 'CPU',
create_account: 'Create Account',
create_free_account: 'Create Free Account',
create_desktop_shortcut: 'Create Shortcut (Desktop)',
@@ -92,6 +97,7 @@ const en = {
credits: 'Credits',
current_password: 'Current Password',
cut: 'Cut',
client_information: 'Client Information',
clock: 'Clock',
clock_visible_hide: 'Hide - Always hidden',
clock_visible_show: 'Show - Always visible',
@@ -114,6 +120,7 @@ const en = {
disable_2fa_confirm: 'Are you sure you want to disable 2FA?',
disable_2fa_instructions: 'Enter your password to disable 2FA.',
disassociate_dir: 'Disassociate Directory',
disk_storage: 'Disk Storage',
documents: 'Documents',
dont_allow: 'Don\'t Allow',
download: 'Download',
@@ -203,7 +210,9 @@ const en = {
open_with: 'Open With',
original_name: 'Original Name',
original_path: 'Original Path',
os: 'Operating System',
oss_code_and_content: 'Open Source Software and Content',
os_version: 'OS Version',
password: 'Password',
password_changed: 'Password changed.',
password_recovery_rate_limit: "You've reached our rate-limit; please wait a few minutes. To prevent this in the future, avoid reloading the page too many times.",
@@ -220,6 +229,7 @@ const en = {
pick_name_for_worker: 'Pick a name for your worker:',
picture: 'Picture',
pictures: 'Pictures',
pixel_ratio: 'Pixel Ratio',
plural_suffix: 's',
powered_by_puter_js: 'Powered by {{link=docs}}Puter.js{{/link}}',
preparing: 'Preparing...',
@@ -239,6 +249,7 @@ const en = {
publish_as_website: 'Publish as website',
publish_as_serverless_worker: 'Publish as Worker',
puter_description: 'Puter is a privacy-first personal cloud to keep all your files, apps, and games in one secure place, accessible from anywhere at any time.',
ram: 'RAM',
reading: 'Reading %strong%',
writing: 'Writing %strong%',
recent: 'Recent',
@@ -268,6 +279,7 @@ const en = {
scan_qr_c2a: 'Scan the code below\nto log into this session from other devices',
scan_qr_2fa: 'Scan the QR code with your authenticator app',
scan_qr_generic: 'Scan this QR code using your phone or another device',
screen_resolution: 'Screen Resolution',
search: 'Search',
seconds: 'seconds',
security: 'Security',
@@ -277,6 +289,7 @@ const en = {
sessions: 'Sessions',
send: 'Send',
send_password_recovery_email: 'Send Password Recovery Email',
server_information: 'Server Information',
session_saved: 'Thank you for creating an account. This session has been saved.',
settings: 'Settings',
set_new_password: 'Set New Password',
@@ -333,6 +346,7 @@ const en = {
uploading: 'Uploading',
uploading_file: 'Uploading %%',
upload_here: 'Upload here',
uptime: 'Uptime',
used_of: '{{used}} used of {{available}}',
usage: 'Usage',
username: 'Username',
+17
View File
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
<title>browser</title>
<desc>Created with Sketch Beta.</desc>
<defs>
</defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
<g id="Icon-Set" sketch:type="MSLayerGroup" transform="translate(-256.000000, -671.000000)" fill="#000000">
<path d="M265,675 C264.448,675 264,675.448 264,676 C264,676.553 264.448,677 265,677 C265.552,677 266,676.553 266,676 C266,675.448 265.552,675 265,675 L265,675 Z M269,675 C268.448,675 268,675.448 268,676 C268,676.553 268.448,677 269,677 C269.552,677 270,676.553 270,676 C270,675.448 269.552,675 269,675 L269,675 Z M286,679 L258,679 L258,675 C258,673.896 258.896,673 260,673 L284,673 C285.104,673 286,673.896 286,675 L286,679 L286,679 Z M286,699 C286,700.104 285.104,701 284,701 L260,701 C258.896,701 258,700.104 258,699 L258,681 L286,681 L286,699 L286,699 Z M284,671 L260,671 C257.791,671 256,672.791 256,675 L256,699 C256,701.209 257.791,703 260,703 L284,703 C286.209,703 288,701.209 288,699 L288,675 C288,672.791 286.209,671 284,671 L284,671 Z M261,675 C260.448,675 260,675.448 260,676 C260,676.553 260.448,677 261,677 C261.552,677 262,676.553 262,676 C262,675.448 261.552,675 261,675 L261,675 Z" id="browser" sketch:type="MSShapeGroup">
</path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14 12.5001C14 13.3285 13.3284 14.0001 12.5 14.0001C11.6716 14.0001 11 13.3285 11 12.5001C11 11.6717 11.6716 11.0001 12.5 11.0001C13.3284 11.0001 14 11.6717 14 12.5001Z" fill="#0F0F0F"/>
<path d="M16.5 10.0001C17.3284 10.0001 18 9.32854 18 8.50011C18 7.67169 17.3284 7.00011 16.5 7.00011C15.6716 7.00011 15 7.67169 15 8.50011C15 9.32854 15.6716 10.0001 16.5 10.0001Z" fill="#0F0F0F"/>
<path d="M13 6.50011C13 7.32854 12.3284 8.00011 11.5 8.00011C10.6716 8.00011 10 7.32854 10 6.50011C10 5.67169 10.6716 5.00011 11.5 5.00011C12.3284 5.00011 13 5.67169 13 6.50011Z" fill="#0F0F0F"/>
<path d="M7.50001 12.0001C8.32844 12.0001 9.00001 11.3285 9.00001 10.5001C9.00001 9.67169 8.32844 9.00011 7.50001 9.00011C6.67158 9.00011 6.00001 9.67169 6.00001 10.5001C6.00001 11.3285 6.67158 12.0001 7.50001 12.0001Z" fill="#0F0F0F"/>
<path d="M14 17.5001C14 18.3285 13.3284 19.0001 12.5 19.0001C11.6716 19.0001 11 18.3285 11 17.5001C11 16.6717 11.6716 16.0001 12.5 16.0001C13.3284 16.0001 14 16.6717 14 17.5001Z" fill="#0F0F0F"/>
<path d="M7.50001 17.0001C8.32844 17.0001 9.00001 16.3285 9.00001 15.5001C9.00001 14.6717 8.32844 14.0001 7.50001 14.0001C6.67158 14.0001 6.00001 14.6717 6.00001 15.5001C6.00001 16.3285 6.67158 17.0001 7.50001 17.0001Z" fill="#0F0F0F"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.5017 1.02215C15.4049 0.791746 19.5636 2.32444 21.8087 5.41131C22.5084 6.37324 22.8228 7.63628 22.6489 8.83154C22.471 10.054 21.7734 11.2315 20.4472 11.8945C19.6389 12.2987 18.7731 12.9466 18.2401 13.668C17.7158 14.3778 17.6139 14.9917 17.8944 15.5529C18.4231 16.6102 18.8894 17.9257 18.8106 19.1875C18.7699 19.8375 18.5828 20.4946 18.1664 21.0799C17.7488 21.6667 17.1448 22.1192 16.3714 22.4286C14.6095 23.1333 12.6279 23.1643 10.8081 22.8207C8.98579 22.4765 7.24486 21.7421 5.92656 20.8194C4.00568 19.4748 2.47455 17.6889 1.71371 15.4464C0.9504 13.1965 0.995912 10.5851 2.06024 7.65803C3.64355 3.30372 7.56248 1.25469 11.5017 1.02215ZM11.6196 3.01868C8.26589 3.21665 5.18483 4.9176 3.93984 8.34149C3.00414 10.9148 3.01388 13.0536 3.60768 14.8038C4.20395 16.5613 5.42282 18.0255 7.07347 19.1809C8.14405 19.9303 9.6169 20.5604 11.1792 20.8554C12.7442 21.151 14.3181 21.0959 15.6286 20.5716C16.308 20.2999 16.7678 19.8099 16.8145 19.0627C16.8606 18.3245 16.5769 17.3901 16.1056 16.4473C15.3639 14.9639 15.8542 13.5318 16.6315 12.4796C17.4002 11.4391 18.5455 10.6093 19.5528 10.1057C20.2266 9.76878 20.5747 9.19623 20.6697 8.54355C20.7686 7.86365 20.5831 7.12638 20.1913 6.58769C18.4364 4.17486 15.0093 2.81858 11.6196 3.01868Z" fill="#0F0F0F"/>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

+24
View File
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg fill="#000000" height="800px" width="800px" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 431.908 431.908" xml:space="preserve">
<g>
<path d="M423.908,152.051c4.418,0,8-3.582,8-8s-3.582-8-8-8h-24.006v-31.936h24.006c4.418,0,8-3.582,8-8s-3.582-8-8-8h-24.006
V71.996c0-22.056-17.944-40-40-40h-16.109V8c0-4.418-3.582-8-8-8s-8,3.582-8,8v23.996h-31.937V8c0-4.418-3.582-8-8-8s-8,3.582-8,8
v23.996H247.92V8c0-4.418-3.582-8-8-8s-8,3.582-8,8v23.996h-31.935V8c0-4.418-3.582-8-8-8s-8,3.582-8,8v23.996h-31.936V8
c0-4.418-3.582-8-8-8s-8,3.582-8,8v23.996h-31.936V8c0-4.418-3.582-8-8-8s-8,3.582-8,8v23.996H71.985c-22.056,0-40,17.944-40,40
v16.119H8c-4.418,0-8,3.582-8,8s3.582,8,8,8h23.985v31.936H8c-4.418,0-8,3.582-8,8s3.582,8,8,8h23.985v31.936H8
c-4.418,0-8,3.582-8,8s3.582,8,8,8h23.985v31.935H8c-4.418,0-8,3.582-8,8s3.582,8,8,8h23.985v31.936H8c-4.418,0-8,3.582-8,8
s3.582,8,8,8h23.985v31.936H8c-4.418,0-8,3.582-8,8s3.582,8,8,8h23.985v16.119c0,22.056,17.944,40,40,40h16.128v23.995
c0,4.418,3.582,8,8,8s8-3.582,8-8v-23.995h31.936v23.995c0,4.418,3.582,8,8,8s8-3.582,8-8v-23.995h31.936v23.995
c0,4.418,3.582,8,8,8s8-3.582,8-8v-23.995h31.935v23.995c0,4.418,3.582,8,8,8s8-3.582,8-8v-23.995h31.936v23.995
c0,4.418,3.582,8,8,8s8-3.582,8-8v-23.995h31.937v23.995c0,4.418,3.582,8,8,8s8-3.582,8-8v-23.995h16.109c22.056,0,40-17.944,40-40
v-16.119h24.006c4.418,0,8-3.582,8-8s-3.582-8-8-8h-24.006v-31.936h24.006c4.418,0,8-3.582,8-8s-3.582-8-8-8h-24.006v-31.936
h24.006c4.418,0,8-3.582,8-8s-3.582-8-8-8h-24.006v-31.935h24.006c4.418,0,8-3.582,8-8s-3.582-8-8-8h-24.006v-31.936H423.908z
M383.902,359.912c0,13.233-10.767,24-24,24H71.985c-13.233,0-24-10.767-24-24V71.996c0-13.234,10.767-24,24-24h287.917
c13.233,0,24,10.766,24,24V359.912z"/>
<path d="M343.777,72.121H88.11c-8.822,0-16,7.177-16,16v255.667c0,8.822,7.178,16,16,16h255.667c8.822,0,16-7.178,16-16V88.121
C359.777,79.298,352.599,72.121,343.777,72.121z M88.11,343.787V88.121h255.667l0.002,255.667H88.11z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg fill="#000000" width="800px" height="800px" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
<path d="M20.86,11.09a2.85,2.85,0,0,1,3,1.25,1,1,0,0,0,1.59-1.21,4.83,4.83,0,0,0-5.13-2,3,3,0,0,0-2.11,2.14A3.23,3.23,0,0,0,19,14.38l4.27,4.26a1.17,1.17,0,0,1,.34.87,1.11,1.11,0,0,1-.41.81,2.74,2.74,0,0,1-4.1-.65,1,1,0,1,0-1.6,1.2,5,5,0,0,0,4,2.13,4.66,4.66,0,0,0,3-1.14,3.15,3.15,0,0,0,1.12-2.26,3.12,3.12,0,0,0-.92-2.37L20.46,13a1.19,1.19,0,0,1-.3-1.15A1,1,0,0,1,20.86,11.09Z"/>
<path d="M16,0A16,16,0,1,0,32,16,16,16,0,0,0,16,0Zm0,30A14,14,0,1,1,30,16,14,14,0,0,1,16,30Z"/>
<path d="M10.5,9C7.47,9,5,12.14,5,16s2.47,7,5.5,7S16,19.86,16,16,13.53,9,10.5,9Zm0,12C8.6,21,7,18.71,7,16s1.6-5,3.5-5S14,13.29,14,16,12.4,21,10.5,21Z"/>
</svg>

After

Width:  |  Height:  |  Size: 872 B

+2
View File
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg"><defs><style>.a{fill:none;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;}</style></defs><path class="a" d="M40.5,5.5H7.5a2,2,0,0,0-2,2v33a2,2,0,0,0,2,2h33a2,2,0,0,0,2-2V7.5A2,2,0,0,0,40.5,5.5Z"/><line class="a" x1="5.5" y1="24" x2="42.5" y2="24"/><line class="a" x1="24" y1="5.5" x2="24" y2="42.5"/><line class="a" x1="24" y1="14.75" x2="42.5" y2="14.75"/><line class="a" x1="33.25" y1="5.5" x2="33.25" y2="42.5"/><line class="a" x1="37.875" y1="5.5" x2="37.875" y2="42.5"/><line class="a" x1="28.625" y1="24" x2="28.625" y2="42.5"/><line class="a" x1="19.375" y1="33.25" x2="19.375" y2="42.5"/><line class="a" x1="10.125" y1="33.25" x2="10.125" y2="42.5"/><line class="a" x1="14.75" y1="42.5" x2="14.75" y2="24"/><line class="a" x1="5.5" y1="33.25" x2="42.5" y2="33.25"/><line class="a" x1="33.25" y1="35.5625" x2="42.5" y2="35.5625"/><line class="a" x1="33.25" y1="40.1875" x2="42.5" y2="40.1875"/><line class="a" x1="24" y1="28.625" x2="42.5" y2="28.625"/><line class="a" x1="33.25" y1="19.375" x2="42.5" y2="19.375"/><line class="a" x1="33.25" y1="10.125" x2="42.5" y2="10.125"/><line class="a" x1="5.5" y1="37.875" x2="42.5" y2="37.875"/><line class="a" x1="40.1875" y1="33.25" x2="40.1875" y2="42.5"/><line class="a" x1="35.5625" y1="33.25" x2="35.5625" y2="42.5"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

+92
View File
@@ -0,0 +1,92 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg version="1.1" id="_x32_" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 512 512" xml:space="preserve">
<style type="text/css">
.st0{fill:#000000;}
</style>
<g>
<path class="st0" d="M165.394,285.996c1.643-1.643,1.643-4.314,0-5.958l-1.086-1.086c-1.643-1.643-4.314-1.643-5.956,0
l-8.247,8.232l7.058,7.05L165.394,285.996z"/>
<path class="st0" d="M171.249,308.328l8.246-8.232c1.644-1.651,1.644-4.314,0-5.957l-1.1-1.101c-1.643-1.643-4.314-1.643-5.957,0
l-8.232,8.247L171.249,308.328z"/>
<path class="st0" d="M193.581,308.225l-1.086-1.086c-1.643-1.644-4.314-1.644-5.957,0l-8.246,8.232l7.058,7.05l8.232-8.238
C195.224,312.539,195.224,309.868,193.581,308.225z"/>
<path class="st0" d="M207.667,328.276c1.658-1.643,1.658-4.306,0.015-5.95l-1.1-1.1c-1.643-1.644-4.314-1.644-5.958,0l-8.232,8.246
l7.043,7.043L207.667,328.276z"/>
<path class="st0" d="M96.035,341.255l-8.231,8.239c-1.644,1.643-1.644,4.306,0,5.949l1.1,1.101c1.644,1.644,4.3,1.644,5.958,0
l8.232-8.232L96.035,341.255z"/>
<path class="st0" d="M110.135,355.355l-8.232,8.231c-1.643,1.644-1.643,4.315,0,5.958l1.086,1.086c1.644,1.644,4.314,1.644,5.958,0
l8.232-8.232L110.135,355.355z"/>
<path class="st0" d="M124.221,369.441l-8.231,8.239c-1.643,1.644-1.643,4.307,0,5.95l1.085,1.101c1.658,1.643,4.315,1.643,5.958,0
l8.247-8.239L124.221,369.441z"/>
<path class="st0" d="M138.322,383.535l-8.232,8.239c-1.644,1.643-1.644,4.314-0.015,5.95l1.1,1.093
c1.643,1.643,4.314,1.643,5.958,0l8.232-8.232L138.322,383.535z"/>
<path class="st0" d="M220.667,335.319c-1.629-1.635-4.3-1.635-5.942,0.008l-8.247,8.232l7.043,7.043l8.247-8.232
c1.643-1.643,1.643-4.306,0-5.958L220.667,335.319z"/>
<rect x="94.992" y="313.927" transform="matrix(-0.7071 -0.7071 0.7071 -0.7071 19.6226 699.9984)" class="st0" width="119.587" height="64.016"/>
<path class="st0" d="M144.177,405.867c-1.644,1.643-1.644,4.306,0,5.95l1.086,1.093c1.658,1.65,4.314,1.643,5.957,0l8.232-8.239
l-7.043-7.043L144.177,405.867z"/>
<path class="st0" d="M260.974,190.43c1.644-1.65,1.644-4.314,0-5.957l-1.1-1.1c-1.644-1.644-4.314-1.644-5.958,0l-8.232,8.246
l7.043,7.043L260.974,190.43z"/>
<path class="st0" d="M266.828,212.755l8.232-8.239c1.644-1.643,1.644-4.314,0-5.957l-1.086-1.086c-1.643-1.643-4.314-1.643-5.958,0
l-8.246,8.232L266.828,212.755z"/>
<path class="st0" d="M289.161,212.653l-1.1-1.093c-1.643-1.643-4.314-1.643-5.958,0l-8.232,8.246l7.043,7.043l8.246-8.239
C290.804,216.966,290.804,214.303,289.161,212.653z"/>
<path class="st0" d="M303.247,232.704c1.643-1.644,1.643-4.315,0-5.958l-1.101-1.093c-1.643-1.636-4.3-1.636-5.942,0.008
l-8.246,8.232l7.058,7.043L303.247,232.704z"/>
<path class="st0" d="M184.468,260.964c1.644,1.643,4.315,1.643,5.958,0l8.232-8.232l-7.043-7.043l-8.232,8.232
c-1.643,1.643-1.658,4.314,0,5.957L184.468,260.964z"/>
<path class="st0" d="M197.469,268.014c-1.643,1.643-1.643,4.306,0,5.949l1.1,1.101c1.643,1.644,4.3,1.644,5.942,0l8.247-8.239
l-7.058-7.05L197.469,268.014z"/>
<path class="st0" d="M211.57,288.065l1.086,1.086c1.643,1.644,4.314,1.644,5.958,0l8.232-8.232l-7.043-7.043l-8.232,8.232
C209.926,283.751,209.926,286.421,211.57,288.065z"/>
<path class="st0" d="M233.888,287.962l-8.232,8.232c-1.644,1.651-1.644,4.314,0,5.957l1.1,1.101c1.644,1.643,4.3,1.643,5.943-0.008
l8.246-8.232L233.888,287.962z"/>
<path class="st0" d="M316.248,239.747c-1.644-1.644-4.314-1.644-5.958,0l-8.232,8.246l7.043,7.043l8.232-8.239
c1.643-1.651,1.658-4.306,0-5.957L316.248,239.747z"/>
<polygon class="st0" points="185.452,230.715 269.998,315.269 315.264,270.002 230.703,185.449 "/>
<path class="st0" d="M239.757,310.294c-1.643,1.643-1.643,4.314-0.014,5.95l1.1,1.093c1.644,1.65,4.314,1.643,5.957,0l8.232-8.232
l-7.043-7.05L239.757,310.294z"/>
<path class="st0" d="M356.539,94.85c1.644-1.643,1.644-4.314,0-5.957l-1.086-1.086c-1.643-1.643-4.314-1.643-5.958,0l-8.246,8.232
l7.058,7.05L356.539,94.85z"/>
<path class="st0" d="M362.394,117.183l8.246-8.239c1.643-1.643,1.643-4.306,0-5.95l-1.1-1.1c-1.643-1.643-4.314-1.643-5.958,0
l-8.232,8.246L362.394,117.183z"/>
<path class="st0" d="M376.495,131.276l8.232-8.239c1.643-1.643,1.643-4.314,0-5.957l-1.086-1.086c-1.644-1.643-4.314-1.643-5.958,0
l-8.246,8.232L376.495,131.276z"/>
<path class="st0" d="M390.581,145.37l8.232-8.239c1.643-1.644,1.658-4.307,0.014-5.95l-1.1-1.1c-1.644-1.643-4.314-1.643-5.958,0
l-8.232,8.239L390.581,145.37z"/>
<path class="st0" d="M280.049,165.398c1.643,1.643,4.3,1.643,5.958,0l8.232-8.239l-7.058-7.051l-8.232,8.232
c-1.643,1.65-1.643,4.314,0,5.958L280.049,165.398z"/>
<path class="st0" d="M301.281,164.21l-8.232,8.232c-1.644,1.643-1.644,4.314,0,5.957l1.086,1.086c1.643,1.643,4.314,1.643,5.957,0
l8.232-8.232L301.281,164.21z"/>
<path class="st0" d="M315.367,178.296l-8.232,8.238c-1.644,1.644-1.644,4.307,0,5.95l1.1,1.093c1.643,1.651,4.299,1.651,5.942,0
l8.246-8.232L315.367,178.296z"/>
<path class="st0" d="M329.468,192.389l-8.232,8.239c-1.658,1.644-1.658,4.314-0.014,5.95l1.101,1.093
c1.643,1.644,4.314,1.644,5.956,0l8.232-8.232L329.468,192.389z"/>
<path class="st0" d="M411.813,144.174c-1.629-1.636-4.299-1.636-5.943,0.008l-8.246,8.232l7.057,7.043l8.232-8.232
c1.644-1.644,1.644-4.314,0-5.957L411.813,144.174z"/>
<rect x="286.137" y="122.781" transform="matrix(-0.7071 -0.7071 0.7071 -0.7071 481.0893 508.8518)" class="st0" width="119.587" height="64.016"/>
<path class="st0" d="M335.322,214.721c-1.643,1.644-1.643,4.306,0,5.95l1.086,1.093c1.658,1.65,4.314,1.644,5.957,0l8.232-8.239
l-7.043-7.043L335.322,214.721z"/>
<path class="st0" d="M472.31,117.322c-8.1,8.1-21.232,8.092-29.332,0c-8.099-8.1-8.099-21.224,0-29.331l-27.307-27.307
c-8.099,8.099-21.231,8.092-29.331,0c-8.099-8.099-8.099-21.224,0-29.332L354.984,0.003L0,354.981l31.356,31.349
c8.099-8.092,21.232-8.092,29.332,0c8.099,8.107,8.099,21.239,0,29.332l27.306,27.306c8.1-8.092,21.232-8.092,29.331,0
c8.1,8.107,8.1,21.24,0,29.331l39.69,39.698L512,157.013L472.31,117.322z M469.595,184.239l-13.675,13.682l-14.894-14.9
l-8.965,8.965l14.893,14.894l-13.675,13.682l-14.893-14.9l-8.966,8.966l14.893,14.893l-13.675,13.675l-14.893-14.893l-8.965,8.958
l14.894,14.9l-13.676,13.675l-14.893-14.893l-8.965,8.958l14.893,14.9l-13.675,13.675l-14.894-14.893l-8.965,8.957l14.893,14.9
l-13.675,13.676l-14.893-14.894l-8.966,8.95l14.893,14.908l-13.675,13.675l-14.893-14.893l-8.965,8.958l14.894,14.9l-13.675,13.675
l-14.894-14.893l-8.965,8.958l14.893,14.9l-13.675,13.675l-14.893-14.9l-8.966,8.958l14.908,14.907l-13.69,13.675l-14.893-14.893
l-8.966,8.95l14.908,14.908l-13.69,13.675l-14.893-14.893l-8.965,8.958l14.893,14.901l-13.675,13.675l-14.894-14.901l-8.965,8.958
l14.908,14.901l-13.69,13.682l-14.893-14.9l-8.966,8.957l14.908,14.9l-18.268,18.268l-25.854-25.846
c5.429-11.658,3.346-25.986-6.25-35.59c-9.611-9.603-23.932-11.686-35.582-6.258l-14.805-14.79
c5.429-11.658,3.346-25.986-6.251-35.589c-9.611-9.604-23.931-11.688-35.582-6.258l-17.52-17.513L354.984,15.176l17.519,17.505
c-5.444,11.65-3.36,25.985,6.251,35.582c9.596,9.611,23.932,11.694,35.582,6.258l14.805,14.797
c-5.444,11.666-3.36,25.986,6.25,35.582c9.596,9.611,23.932,11.695,35.582,6.259l25.854,25.854l-18.268,18.268l-14.893-14.901
l-8.966,8.966L469.595,184.239z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.3 KiB

+4
View File
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg fill="#000000" width="800px" height="800px" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg">
<path d="M30 2.994h-28c-1.099 0-2 0.9-2 2v17.006c0 1.099 0.9 1.999 2 1.999h13v3.006h-5c-0.552 0-1 0.448-1 1s0.448 1 1 1h12c0.552 0 1-0.448 1-1s-0.448-1-1-1h-5v-3.006h13c1.099 0 2-0.9 2-1.999v-17.006c0-1.1-0.901-2-2-2zM30 22h-28v-17.006h28v17.006z"></path>
</svg>

After

Width:  |  Height:  |  Size: 504 B

@@ -0,0 +1,2 @@
<?xml version="1.0" ?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg fill="#000000" width="800px" height="800px" viewBox="0 0 35 35" data-name="Layer 2" id="f3cb8d82-36fc-45b3-81de-6922a15d3e7e" xmlns="http://www.w3.org/2000/svg"><path d="M17.5,16.7a2,2,0,1,1,2-2A2,2,0,0,1,17.5,16.7Zm0-2h0Z"/><path d="M28.559,34.75H6.441A4.2,4.2,0,0,1,2.25,30.559V4.442A4.2,4.2,0,0,1,6.441.25H28.559A4.2,4.2,0,0,1,32.75,4.442V30.559A4.2,4.2,0,0,1,28.559,34.75ZM6.441,2.75A1.694,1.694,0,0,0,4.75,4.442V30.559A1.693,1.693,0,0,0,6.441,32.25H28.559a1.693,1.693,0,0,0,1.691-1.691V4.442A1.694,1.694,0,0,0,28.559,2.75Z"/><path d="M17.46,23.445a8.75,8.75,0,1,1,8.75-8.75A8.759,8.759,0,0,1,17.46,23.445Zm0-15a6.25,6.25,0,1,0,6.25,6.25A6.257,6.257,0,0,0,17.46,8.445Z"/><path d="M8.2,7.45a1.251,1.251,0,0,0,0-2.5,1.251,1.251,0,0,0,0,2.5Z"/><path d="M26.8,7.45a1.251,1.251,0,0,0,0-2.5,1.251,1.251,0,0,0,0,2.5Z"/><path d="M8.2,29.67a1.251,1.251,0,0,0,0-2.5,1.251,1.251,0,0,0,0,2.5Z"/><path d="M26.8,29.67a1.251,1.251,0,0,0,0-2.5,1.251,1.251,0,0,0,0,2.5Z"/><path d="M10.46,24.31a1.251,1.251,0,0,1-.89-2.128l4.5-4.56a1.25,1.25,0,0,1,1.78,1.756l-4.5,4.56A1.246,1.246,0,0,1,10.46,24.31Z"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 21C16.9706 21 21 16.9706 21 12C21 7.02944 16.9706 3 12 3C7.02944 3 3 7.02944 3 12C3 16.9706 7.02944 21 12 21Z" stroke="#000000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 6V12" stroke="#000000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M16.24 16.24L12 12" stroke="#000000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 654 B